[
  {
    "path": ".clang-format",
    "content": "---\nLanguage: Cpp\nBasedOnStyle: LLVM\n\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignConsecutiveMacros: true\nAlignConsecutiveAssignments: true\nAlignEscapedNewlines: Right\nAlignOperands: false\nAlignTrailingComments: true\nAllowAllArgumentsOnNextLine: true\nAllowAllConstructorInitializersOnNextLine: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: true\nAllowShortCaseLabelsOnASingleLine: true\nAllowShortFunctionsOnASingleLine: Empty\nAllowShortIfStatementsOnASingleLine: Never\nAllowShortLambdasOnASingleLine: All\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: Yes\nBreakBeforeBraces: Attach\nBreakBeforeTernaryOperators: false\nBreakConstructorInitializers: AfterColon\nColumnLimit: 180\nCompactNamespaces: false\nConstructorInitializerAllOnOneLineOrOnePerLine: false\nExperimentalAutoDetectBinPacking: false\nFixNamespaceComments: false\nIncludeBlocks: Preserve\nIndentCaseLabels: true\nIndentWidth: 4\nPointerAlignment: Left\nReflowComments: false\nSortIncludes: false\nSortUsingDeclarations: false\nSpaceAfterCStyleCast: false\nSpaceAfterLogicalNot: false\nSpaceAfterTemplateKeyword: true\nSpaceBeforeCtorInitializerColon: true\nSpaceBeforeInheritanceColon: true\nSpaceBeforeParens: ControlStatements\nSpaceBeforeRangeBasedForLoopColon: true\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles: false\nSpacesInCStyleCastParentheses: false\nSpacesInContainerLiterals: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard: Auto\nTabWidth: 4\nUseTab: Never\n\nAllowShortEnumsOnASingleLine: false\n\nBraceWrapping:\n  AfterEnum: false\n\nAlignConsecutiveDeclarations: AcrossEmptyLines\n\nNamespaceIndentation: All\n"
  },
  {
    "path": ".clang-format-ignore",
    "content": "subprojects/**/*\n"
  },
  {
    "path": ".clang-tidy",
    "content": "WarningsAsErrors: >\n  -*,\n  bugprone-*,\n  -bugprone-multi-level-implicit-pointer-conversion,\n  -bugprone-empty-catch,\n  -bugprone-unused-return-value,\n  -bugprone-reserved-identifier,\n  -bugprone-switch-missing-default-case,\n  -bugprone-unused-local-non-trivial-variable,\n  -bugprone-easily-swappable-parameters,\n  -bugprone-forward-declararion-namespace,\n  -bugprone-forward-declararion-namespace,\n  -bugprone-macro-parentheses,\n  -bugprone-narrowing-conversions,\n  -bugprone-branch-clone,\n  -bugprone-assignment-in-if-condition,\n  concurrency-*,\n  -concurrency-mt-unsafe,\n  cppcoreguidelines-*,\n  -cppcoreguidelines-pro-type-const-cast,\n  -cppcoreguidelines-owning-memory,\n  -cppcoreguidelines-avoid-magic-numbers,\n  -cppcoreguidelines-pro-bounds-constant-array-index,\n  -cppcoreguidelines-avoid-const-or-ref-data-members,\n  -cppcoreguidelines-non-private-member-variables-in-classes,\n  -cppcoreguidelines-avoid-goto,\n  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\n  -cppcoreguidelines-avoid-do-while,\n  -cppcoreguidelines-avoid-non-const-global-variables,\n  -cppcoreguidelines-special-member-functions,\n  -cppcoreguidelines-explicit-virtual-functions,\n  -cppcoreguidelines-avoid-c-arrays,\n  -cppcoreguidelines-pro-bounds-pointer-arithmetic,\n  -cppcoreguidelines-narrowing-conversions,\n  -cppcoreguidelines-pro-type-union-access,\n  -cppcoreguidelines-pro-type-member-init,\n  -cppcoreguidelines-macro-usage,\n  -cppcoreguidelines-macro-to-enum,\n  -cppcoreguidelines-init-variables,\n  -cppcoreguidelines-pro-type-cstyle-cast,\n  -cppcoreguidelines-pro-type-vararg,\n  -cppcoreguidelines-pro-type-reinterpret-cast,\n  -google-global-names-in-headers,\n  -google-readability-casting,\n  google-runtime-operator,\n  misc-*,\n  -misc-use-internal-linkage,\n  -misc-unused-parameters,\n  -misc-no-recursion,\n  -misc-non-private-member-variables-in-classes,\n  -misc-include-cleaner,\n  -misc-use-anonymous-namespace,\n  -misc-const-correctness,\n  modernize-*,\n  -modernize-use-emplace,\n  -modernize-redundant-void-arg,\n  -modernize-use-starts-ends-with,\n  -modernize-use-designated-initializers,\n  -modernize-use-std-numbers,\n  -modernize-return-braced-init-list,\n  -modernize-use-trailing-return-type,\n  -modernize-use-using,\n  -modernize-use-override,\n  -modernize-avoid-c-arrays,\n  -modernize-macro-to-enum,\n  -modernize-loop-convert,\n  -modernize-use-nodiscard,\n  -modernize-pass-by-value,\n  -modernize-use-auto,\n  performance-*,\n  -performance-inefficient-vector-operation,\n  -performance-inefficient-string-concatenation,\n  -performance-enum-size,\n  -performance-move-const-arg,\n  -performance-avoid-endl,\n  -performance-unnecessary-value-param,\n  portability-std-allocator-const,\n  readability-*,\n  -readability-identifier-naming,\n  -readability-use-std-min-max,\n  -readability-math-missing-parentheses,\n  -readability-simplify-boolean-expr,\n  -readability-static-accessed-through-instance,\n  -readability-use-anyofallof,\n  -readability-enum-initial-value,\n  -readability-redundant-inline-specifier,\n  -readability-function-cognitive-complexity,\n  -readability-function-size,\n  -readability-identifier-length,\n  -readability-magic-numbers,\n  -readability-uppercase-literal-suffix,\n  -readability-braces-around-statements,\n  -readability-redundant-access-specifiers,\n  -readability-else-after-return,\n  -readability-container-data-pointer,\n  -readability-implicit-bool-conversion,\n  -readability-avoid-nested-conditional-operator,\n  -readability-redundant-member-init,\n  -readability-redundant-string-init,\n  -readability-avoid-const-params-in-decls,\n  -readability-named-parameter,\n  -readability-convert-member-functions-to-static,\n  -readability-qualified-auto,\n  -readability-make-member-function-const,\n  -readability-isolate-declaration,\n  -readability-inconsistent-declaration-parameter-name,\n  -clang-diagnostic-error,\n\nHeaderFilterRegex: '.*\\.hpp'\nFormatStyle: file\nChecks: >\n  -*,\n  bugprone-*,\n  -bugprone-easily-swappable-parameters,\n  -bugprone-forward-declararion-namespace,\n  -bugprone-forward-declararion-namespace,\n  -bugprone-macro-parentheses,\n  -bugprone-narrowing-conversions,\n  -bugprone-branch-clone,\n  -bugprone-assignment-in-if-condition,\n  concurrency-*,\n  -concurrency-mt-unsafe,\n  cppcoreguidelines-*,\n  -cppcoreguidelines-owning-memory,\n  -cppcoreguidelines-avoid-magic-numbers,\n  -cppcoreguidelines-pro-bounds-constant-array-index,\n  -cppcoreguidelines-avoid-const-or-ref-data-members,\n  -cppcoreguidelines-non-private-member-variables-in-classes,\n  -cppcoreguidelines-avoid-goto,\n  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\n  -cppcoreguidelines-avoid-do-while,\n  -cppcoreguidelines-avoid-non-const-global-variables,\n  -cppcoreguidelines-special-member-functions,\n  -cppcoreguidelines-explicit-virtual-functions,\n  -cppcoreguidelines-avoid-c-arrays,\n  -cppcoreguidelines-pro-bounds-pointer-arithmetic,\n  -cppcoreguidelines-narrowing-conversions,\n  -cppcoreguidelines-pro-type-union-access,\n  -cppcoreguidelines-pro-type-member-init,\n  -cppcoreguidelines-macro-usage,\n  -cppcoreguidelines-macro-to-enum,\n  -cppcoreguidelines-init-variables,\n  -cppcoreguidelines-pro-type-cstyle-cast,\n  -cppcoreguidelines-pro-type-vararg,\n  -cppcoreguidelines-pro-type-reinterpret-cast,\n  google-global-names-in-headers,\n  -google-readability-casting,\n  google-runtime-operator,\n  misc-*,\n  -misc-unused-parameters,\n  -misc-no-recursion,\n  -misc-non-private-member-variables-in-classes,\n  -misc-include-cleaner,\n  -misc-use-anonymous-namespace,\n  -misc-const-correctness,\n  modernize-*,\n  -modernize-return-braced-init-list,\n  -modernize-use-trailing-return-type,\n  -modernize-use-using,\n  -modernize-use-override,\n  -modernize-avoid-c-arrays,\n  -modernize-macro-to-enum,\n  -modernize-loop-convert,\n  -modernize-use-nodiscard,\n  -modernize-pass-by-value,\n  -modernize-use-auto,\n  performance-*,\n  -performance-avoid-endl,\n  -performance-unnecessary-value-param,\n  portability-std-allocator-const,\n  readability-*,\n  -readability-function-cognitive-complexity,\n  -readability-function-size,\n  -readability-identifier-length,\n  -readability-magic-numbers,\n  -readability-uppercase-literal-suffix,\n  -readability-braces-around-statements,\n  -readability-redundant-access-specifiers,\n  -readability-else-after-return,\n  -readability-container-data-pointer,\n  -readability-implicit-bool-conversion,\n  -readability-avoid-nested-conditional-operator,\n  -readability-redundant-member-init,\n  -readability-redundant-string-init,\n  -readability-avoid-const-params-in-decls,\n  -readability-named-parameter,\n  -readability-convert-member-functions-to-static,\n  -readability-qualified-auto,\n  -readability-make-member-function-const,\n  -readability-isolate-declaration,\n  -readability-inconsistent-declaration-parameter-name,\n  -clang-diagnostic-error,\n\nCheckOptions:\n  performance-for-range-copy.WarnOnAllAutoCopies: true\n  performance-inefficient-string-concatenation.StrictMode: true\n  readability-braces-around-statements.ShortStatementLines: 0\n  readability-identifier-naming.ClassCase: CamelCase\n  readability-identifier-naming.ClassIgnoredRegexp: I.*\n  readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?\n  readability-identifier-naming.EnumCase: CamelCase\n  readability-identifier-naming.EnumPrefix: e\n  readability-identifier-naming.EnumConstantCase: UPPER_CASE\n  readability-identifier-naming.FunctionCase: camelBack\n  readability-identifier-naming.NamespaceCase: CamelCase\n  readability-identifier-naming.NamespacePrefix: N\n  readability-identifier-naming.StructPrefix: S\n  readability-identifier-naming.StructCase: CamelCase\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: vaxry\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: Do not open issues, go to discussions please!\ndescription: Do not open an issue\nbody:\n  - type: checkboxes\n    attributes:\n      label: Please close this issue.\n      description: Users cannot open issues. I want my issue to be closed.\n      options:\n        - label: Yes, I want this issue to be closed.\n          required: true\n\n  - type: textarea\n    id: body\n    attributes:\n      label: Issue body\n"
  },
  {
    "path": ".github/actions/setup_base/action.yml",
    "content": "name: \"Setup base\"\n\ninputs:\n  INSTALL_XORG_PKGS:\n    description: 'Install xorg dependencies'\n    required: false\n    default: false\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: Get required pacman pkgs\n      shell: bash\n      run: |\n        sed -i 's/SigLevel    = Required DatabaseOptional/SigLevel    = Optional TrustAll/' /etc/pacman.conf\n        pacman --noconfirm --noprogressbar -Syyu\n        pacman --noconfirm --noprogressbar -Sy \\\n          base-devel \\\n          cairo \\\n          clang \\\n          cmake \\\n          git \\\n          glaze \\\n          glm \\\n          glslang \\\n          go \\\n          gtest \\\n          hyprlang \\\n          hyprcursor \\\n          jq \\\n          libc++ \\\n          libdisplay-info \\\n          libdrm \\\n          libepoxy \\\n          libfontenc \\\n          libglvnd \\\n          libinput \\\n          libjxl \\\n          libliftoff \\\n          libspng \\\n          libwebp \\\n          libxcursor \\\n          libxcvt \\\n          libxfont2 \\\n          libxkbcommon \\\n          libxkbfile \\\n          lld \\\n          meson \\\n          muparser \\\n          ninja \\\n          pango \\\n          pixman \\\n          pkgconf \\\n          pugixml \\\n          scdoc \\\n          seatd \\\n          systemd \\\n          tomlplusplus \\\n          wayland \\\n          wayland-protocols \\\n          xcb-util-errors \\\n          xcb-util-renderutil \\\n          xcb-util-wm \\\n          xcb-util \\\n          xcb-util-image \\\n          libzip \\\n          librsvg \\\n          re2\n\n    - name: Get hyprwayland-scanner-git\n      shell: bash\n      run: |\n        git clone https://github.com/hyprwm/hyprwayland-scanner --recursive\n        cd hyprwayland-scanner\n        cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build\n        cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`\n        cmake --install build\n\n    - name: Get hyprwire-git\n      shell: bash\n      run: |\n        git clone https://github.com/hyprwm/hyprwire --recursive\n        cd hyprwire\n        cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build\n        cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`\n        cmake --install build\n\n    - name: Get hyprutils-git\n      shell: bash\n      run: |\n        git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build\n\n    - name: Get hyprgraphics-git\n      shell: bash\n      run: |\n        git clone https://github.com/hyprwm/hyprgraphics && cd hyprgraphics && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprgraphics && cmake --install build\n\n    - name: Get aquamarine-git\n      shell: bash\n      run: |\n        git clone https://github.com/hyprwm/aquamarine && cd aquamarine && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target aquamarine && cmake --install build\n\n    - name: Get Xorg pacman pkgs\n      shell: bash\n      if: inputs.INSTALL_XORG_PKGS == 'true'\n      run: |\n        pacman --noconfirm --noprogressbar -Sy \\\n          xorg-fonts-encodings \\\n          xorg-server-common \\\n          xorg-setxkbmap \\\n          xorg-xkbcomp \\\n          xorg-xwayland\n\n    - name: Checkout Hyprland\n      uses: actions/checkout@v4\n      with:\n        submodules: recursive\n\n    # Fix an issue with actions/checkout where the checkout repo is not mark as safe\n    - name: Mark directory as safe for git\n      shell: bash\n      run: |\n        git config --global --add safe.directory /__w/Hyprland/Hyprland\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "assets:\n  - changed-files:\n      - any-glob-to-any-file: \"assets/**\"\n\ndocs:\n  - changed-files:\n      - any-glob-to-any-file: \"docs/**\"\n\nhyprctl:\n  - changed-files:\n      - any-glob-to-any-file: \"hyprctl/**\"\n\nhyprpm:\n  - changed-files:\n      - any-glob-to-any-file: \"hyprpm/**\"\n\nnix:\n  - changed-files:\n      - any-glob-to-any-file: \"nix/**\"\n\nprotocols:\n  - changed-files:\n      - any-glob-to-any-file: [\"protocols/**\", \"src/protocols/**\"]\n\nstart:\n  - changed-files:\n      - any-glob-to-any-file: \"start/**\"\n\ncore:\n  - changed-files:\n      - any-glob-to-any-file: \"src/**\"\n\nconfig:\n  - changed-files:\n      - any-glob-to-any-file: \"src/config/**\"\n\ndebug:\n  - changed-files:\n      - any-glob-to-any-file: \"src/debug/**\"\n\ndesktop:\n  - changed-files:\n      - any-glob-to-any-file: \"src/desktop/**\"\n\ndevices:\n  - changed-files:\n      - any-glob-to-any-file: \"src/devices/**\"\n\nevents:\n  - changed-files:\n      - any-glob-to-any-file: \"src/events/**\"\n\nhelpers:\n  - changed-files:\n      - any-glob-to-any-file: \"src/helpers/**\"\n\nhyprerror:\n  - changed-files:\n      - any-glob-to-any-file: \"src/hyprerror/**\"\n\ninit:\n  - changed-files:\n      - any-glob-to-any-file: \"src/init/**\"\n\nlayout:\n  - changed-files:\n      - any-glob-to-any-file: \"src/layout/**\"\n\nmanagers:\n  - changed-files:\n      - any-glob-to-any-file: \"src/managers/**\"\n\npch:\n  - changed-files:\n      - any-glob-to-any-file: \"src/pch/**\"\n\nplugins:\n  - changed-files:\n      - any-glob-to-any-file: \"src/plugins/**\"\n\nrender:\n  - changed-files:\n      - any-glob-to-any-file: \"src/render/**\"\n\nxwayland:\n  - changed-files:\n      - any-glob-to-any-file: \"src/xwayland/**\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nBEFORE you submit your PR, please check out the PR guidelines\non our wiki: https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/\n\nUsing an AI tool, or you are an AI agent? Check our AI Policy first: https://github.com/hyprwm/.github/blob/main/policies/AI_USAGE.md\n-->\n\n\n#### Describe your PR, what does it fix/add?\n\n\n#### Is there anything you want to mention? (unchecked code, possible bugs, found problems, breaking compatibility, etc.)\n\n\n#### Is it ready for merging, or does it need work?\n\n\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: Build Hyprland\n\non: [push, pull_request, workflow_dispatch]\njobs:\n  gcc:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork\n    name: \"Build Hyprland (Arch)\"\n    runs-on: ubuntu-latest\n    container:\n      image: archlinux\n    steps:\n      - name: Checkout repository actions\n        uses: actions/checkout@v4\n        with:\n          sparse-checkout: .github/actions\n\n      - name: Setup base\n        uses: ./.github/actions/setup_base\n        with:\n          INSTALL_XORG_PKGS: true\n\n      - name: Build Hyprland\n        run: |\n          CFLAGS=-Werror CXXFLAGS=-Werror make nopch\n\n      - name: Compress and package artifacts\n        run: |\n          mkdir x86_64-pc-linux-gnu\n          mkdir hyprland\n          cp ./LICENSE hyprland/\n          cp build/Hyprland hyprland/\n          cp build/hyprctl/hyprctl hyprland/\n          cp build/hyprpm/hyprpm hyprland/\n          cp -r example/ hyprland/\n          cp -r assets/ hyprland/\n          tar -cvJf Hyprland.tar.xz hyprland\n\n      - name: Release\n        uses: actions/upload-artifact@v4\n        with:\n          name: Build archive\n          path: Hyprland.tar.xz\n\n  clang-format:\n    permissions: read-all\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork\n    name: \"Code Style\"\n    runs-on: ubuntu-latest\n    container:\n      image: archlinux\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      # - name: clang-format check\n      #   uses: jidicula/clang-format-action@v4.16.0\n      #   with:\n      #     exclude-regex: ^subprojects$\n\n      - name: Install clang-format\n        run: |\n          pacman --noconfirm --noprogressbar -Syyu\n          pacman --noconfirm --noprogressbar -Sy clang\n\n      - name: clang-format check\n        run: .github/workflows/clang-format-check.sh \".\" \"llvm\" \"^subprojects$\" \"\"\n\n      - name: Save PR head commit SHA\n        if: failure() && github.event_name == 'pull_request'\n        shell: bash\n        run: |\n          SHA=\"${{ github.event.pull_request.head.sha }}\"\n          echo \"SHA=$SHA\" >> $GITHUB_ENV\n      - name: Save latest commit SHA if not PR\n        if: failure() && github.event_name != 'pull_request'\n        shell: bash\n        run: echo \"SHA=${{ github.sha }}\" >> $GITHUB_ENV\n\n      - name: Report failure in job summary\n        if: failure()\n        run: |\n          DEEPLINK=\"${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}\"\n          echo -e \"Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\\n$(<$GITHUB_WORKSPACE/failing-files.txt)\" >> $GITHUB_STEP_SUMMARY\n"
  },
  {
    "path": ".github/workflows/clang-format-check.sh",
    "content": "#!/usr/bin/env bash\n#\n# Adapted from https://github.com/jidicula/clang-format-action\n\n###############################################################################\n#                                check.sh                                     #\n###############################################################################\n# USAGE: ./entrypoint.sh [<path>] [<fallback style>]\n#\n# Checks all C/C++/Protobuf/CUDA files (.h, .H, .hpp, .hh, .h++, .hxx and .c,\n# .C, .cpp, .cc, .c++, .cxx, .proto, .cu) in the provided GitHub repository path\n# (arg1) for conforming to clang-format. If no path is provided or provided path\n# is not a directory, all C/C++/Protobuf/CUDA files are checked. If any files\n# are incorrectly formatted, the script lists them and exits with 1.\n#\n# Define your own formatting rules in a .clang-format file at your repository\n# root. Otherwise, the provided style guide (arg2) is used as a fallback.\n\n# format_diff function\n# Accepts a filepath argument. The filepath passed to this function must point\n# to a C/C++/Protobuf/CUDA file.\nformat_diff() {\n  local filepath=\"$1\"\n\n  # Invoke clang-format with dry run and formatting error output\n  local_format=\"$(clang-format \\\n    --dry-run \\\n    --Werror \\\n    --style=file \\\n    --fallback-style=\"$FALLBACK_STYLE\" \\\n    \"${filepath}\")\"\n\n  local format_status=\"$?\"\n  if [[ ${format_status} -ne 0 ]]; then\n    # Append Markdown-bulleted monospaced filepath of failing file to\n    # summary file.\n    echo \"* \\`$filepath\\`\" >>failing-files.txt\n\n    echo \"Failed on file: $filepath\" >&2\n    echo \"$local_format\" >&2\n    exit_code=1 # flip the global exit code\n    return \"${format_status}\"\n  fi\n  return 0\n}\n\nCHECK_PATH=\"$1\"\nFALLBACK_STYLE=\"$2\"\nEXCLUDE_REGEX=\"$3\"\nINCLUDE_REGEX=\"$4\"\n\n# Set the regex to an empty string regex if nothing was provided\nif [[ -z $EXCLUDE_REGEX ]]; then\n  EXCLUDE_REGEX=\"^$\"\nfi\n\n# Set the filetype regex if nothing was provided.\n# Find all C/C++/Protobuf/CUDA files:\n#   h, H, hpp, hh, h++, hxx\n#   c, C, cpp, cc, c++, cxx\n#   ino, pde\n#   proto\n#   cu\nif [[ -z $INCLUDE_REGEX ]]; then\n  INCLUDE_REGEX='^.*\\.((((c|C)(c|pp|xx|\\+\\+)?$)|((h|H)h?(pp|xx|\\+\\+)?$))|(ino|pde|proto|cu))$'\nfi\n\ncd \"$GITHUB_WORKSPACE\" || exit 2\n\nif [[ ! -d $CHECK_PATH ]]; then\n  echo \"Not a directory in the workspace, fallback to all files.\" >&2\n  CHECK_PATH=\".\"\nfi\n\n# initialize exit code\nexit_code=0\n\n# All files improperly formatted will be printed to the output.\nsrc_files=$(find \"$CHECK_PATH\" -name .git -prune -o -regextype posix-egrep -regex \"$INCLUDE_REGEX\" -print)\n\n# check formatting in each source file\nIFS=$'\\n' # Loop below should separate on new lines, not spaces.\nfor file in $src_files; do\n  # Only check formatting if the path doesn't match the regex\n  if ! [[ ${file} =~ $EXCLUDE_REGEX ]]; then\n    format_diff \"${file}\"\n  fi\ndone\n\n# global exit code is flipped to nonzero if any invocation of `format_diff` has\n# a formatting difference.\nexit \"$exit_code\"\n"
  },
  {
    "path": ".github/workflows/clang-format.yml",
    "content": "name: clang-format\non: pull_request_target\njobs:\n  clang-format:\n    permissions: write-all\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork\n    name: \"Code Style\"\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: clang-format check\n        uses: jidicula/clang-format-action@v4.16.0\n        with:\n          exclude-regex: ^subprojects$\n\n      - name: Create comment\n        if: ${{ failure() && github.event_name == 'pull_request' }}\n        run: |\n          echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style).' > clang-format.patch\n\n      - name: Post comment\n        if: ${{ failure() && github.event_name == 'pull_request' }}\n        uses: mshick/add-pr-comment@v2\n        with:\n          message-path: |\n            clang-format.patch\n"
  },
  {
    "path": ".github/workflows/close-issues.yml",
    "content": "name: Close Unauthorized Issues\n\non:\n  workflow_dispatch:\n  issues:\n    types: [opened]\n\njobs:\n  close-unauthorized-issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      # XXX: This *could* be done in Bash by abusing GitHub's own tool to interact with its API\n      # but that's too much of a hack, and we'll be adding a layer of abstraction. github-script\n      # is a workflow that eases interaction with GitHub API in the workflow run context.\n      - name: \"Close 'unauthorized' issues\"\n        uses: actions/github-script@v7\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            const ALLOWED_USERS = ['vaxerski', 'fufexan', 'NotAShelf'];\n            const CLOSING_COMMENT = 'Users are no longer allowed to open issues themselves, please open a discussion instead.\\n\\nPlease see the [wiki](https://wiki.hyprland.org/Contributing-and-Debugging/Issue-Guidelines/) on why this is the case.\\n\\nWe are volunteers, and we need your cooperation to make the best software we can. Thank you for understanding! ❤️\\n\\n[Open a discussion here](https://github.com/hyprwm/Hyprland/discussions)';\n\n            async function closeUnauthorizedIssue(issueNumber, userName) {\n              if (ALLOWED_USERS.includes(userName)) {\n                console.log(`Issue #${issueNumber} - Created by authorized user ${userName}`);\n                return;\n              }\n\n              console.log(`Issue #${issueNumber} - Unauthorized, closing`);\n\n              await github.rest.issues.update({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issueNumber,\n                state: 'closed',\n                state_reason: 'not_planned'\n              });\n\n              await github.rest.issues.createComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issueNumber,\n                body: CLOSING_COMMENT\n              });\n            }\n\n            if (context.eventName === 'issues' && context.payload.action === 'opened') {\n              // Direct access to the issue that triggered the workflow\n              const issue = context.payload.issue;\n\n              // Skip if this is a PR\n              if (issue.pull_request) {\n                console.log(`Issue #${issue.number} - Skipping, this is a pull request`);\n                return;\n              }\n\n              // Process the single issue that triggered the workflow\n              await closeUnauthorizedIssue(issue.number, issue.user.login);\n            } else {\n              // For manual runs, we need to handle pagination\n              async function* fetchAllOpenIssues() {\n                let page = 1;\n                let hasNextPage = true;\n\n                while (hasNextPage) {\n                  const response = await github.rest.issues.listForRepo({\n                    owner: context.repo.owner,\n                    repo: context.repo.repo,\n                    state: 'open',\n                    per_page: 100,\n                    page: page\n                  });\n\n                  if (response.data.length === 0) {\n                    hasNextPage = false;\n                  } else {\n                    for (const issue of response.data) {\n                      yield issue;\n                    }\n                    page++;\n                  }\n                }\n              }\n\n              // Process issues one by one\n              for await (const issue of fetchAllOpenIssues()) {\n                try {\n                  // Skip pull requests\n                  if (issue.pull_request) {\n                    console.log(`Issue #${issue.number} - Skipping, this is a pull request`);\n                    continue;\n                  }\n\n                  await closeUnauthorizedIssue(issue.number, issue.user.login);\n                } catch (error) {\n                  console.error(`Error processing issue #${issue.number}: ${error.message}`);\n                }\n              }\n            }\n"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "content": "name: \"Pull Request Labeler\"\non:\n  - pull_request_target\n\njobs:\n  labeler:\n    permissions:\n      contents: read\n      pull-requests: write\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/labeler@v5\n"
  },
  {
    "path": ".github/workflows/man-update.yaml",
    "content": "name: Build man pages\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - docs/**\n    branches:\n      - 'main'\n\njobs:\n  main:\n    name: Build man pages\n    runs-on: ubuntu-latest\n    steps:\n    - name: Install deps\n      run: sudo apt install pandoc\n\n    - name: Clone repository\n      uses: actions/checkout@v4\n      with:\n        token: ${{ secrets.PAT }}\n\n    - name: Build man pages\n      run: make man\n\n    - uses: stefanzweifel/git-auto-commit-action@v5\n      name: Commit\n      with:\n        commit_message: \"[gha] build man pages\"\n"
  },
  {
    "path": ".github/workflows/new-pr-comment.yml",
    "content": "name: \"New MR welcome comment\"\n\non:\n  pull_request_target:\n    types:\n      - opened\n\njobs:\n  comment:\n    if: >\n      github.event.pull_request.user.login != 'vaxerski' &&\n      github.event.pull_request.user.login != 'fufexan' &&\n      github.event.pull_request.user.login != 'gulafaran' &&\n      github.event.pull_request.user.login != 'ujint34' &&\n      github.event.pull_request.user.login != 'paideiadilemma' &&\n      github.event.pull_request.user.login != 'notashelf'\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n\n    env:\n      PR_COMMENT: |\n        Hello and thank you for making a PR to Hyprland!\n\n        Please check the [PR Guidelines](https://wiki.hypr.land/Contributing-and-Debugging/PR-Guidelines/) and make sure your PR follows them.\n        It will make the entire review process faster. :)\n\n        If your code can be tested, please always add tests. See more [here](https://wiki.hypr.land/Contributing-and-Debugging/Tests/).\n\n        _beep boop, I'm just a bot. A real human will review your PR soon._\n\n    steps:\n      - name: Add comment to PR\n        uses: actions/github-script@v7\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            const pr = context.payload.pull_request;\n\n            await github.rest.issues.createComment({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: pr.number,\n              body: process.env.PR_COMMENT,\n            });\n"
  },
  {
    "path": ".github/workflows/nix-ci.yml",
    "content": "name: Nix\n\non: [push, pull_request, workflow_dispatch]\n\njobs:\n  update-inputs:\n    if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch')\n    uses: ./.github/workflows/nix-update-inputs.yml\n    secrets: inherit\n\n  hyprland:\n    if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)\n    uses: ./.github/workflows/nix.yml\n    secrets: inherit\n    with:\n      command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}' -L --extra-substituters \"https://hyprland.cachix.org\"\n\n  xdph:\n    if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)\n    needs: hyprland\n    uses: ./.github/workflows/nix.yml\n    secrets: inherit\n    with:\n      command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters \"https://hyprland.cachix.org\"\n\n  test:\n    if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)\n    uses: ./.github/workflows/nix-test.yml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/nix-test.yml",
    "content": "name: Nix (Test)\n\non:\n  workflow_call:\n    secrets:\n      CACHIX_AUTH_TOKEN:\n        required: false\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Nix\n        uses: nixbuild/nix-quick-install-action@v31\n        with:\n          nix_conf: |\n            keep-env-derivations = true\n            keep-outputs = true\n\n      - name: Restore and save Nix store\n        uses: nix-community/cache-nix-action@v6\n        with:\n          # restore and save a cache using this key (per job)\n          primary-key: nix-${{ runner.os }}-${{ github.job }}\n          # if there's no cache hit, restore a cache by this prefix\n          restore-prefixes-first-match: nix-${{ runner.os }}\n          # collect garbage until the Nix store size (in bytes) is at most this number\n          # before trying to save a new cache\n          gc-max-store-size-linux: 5G\n\n      - uses: cachix/cachix-action@v15\n        with:\n          name: hyprland\n          authToken: \"${{ secrets.CACHIX_AUTH_TOKEN }}\"\n\n      - name: Run test VM\n        run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters \"https://hyprland.cachix.org\"\n\n      - name: Check exit status\n        run: grep 0 result/exit_status\n\n      - name: Upload artifacts\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: logs\n          path: result\n"
  },
  {
    "path": ".github/workflows/nix-update-inputs.yml",
    "content": "name: Nix (Update Inputs)\n\non:\n  workflow_call:\n    secrets:\n      PAT:\n        required: true\n\njobs:\n  update:\n    if: github.repository == 'hyprwm/Hyprland'\n    name: inputs\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.PAT }}\n\n      - name: Install Nix\n        uses: nixbuild/nix-quick-install-action@v31\n        with:\n          nix_conf: |\n            keep-env-derivations = true\n            keep-outputs = true\n\n      - name: Restore and save Nix store\n        uses: nix-community/cache-nix-action@v6\n        with:\n          # restore and save a cache using this key (per job)\n          primary-key: nix-${{ runner.os }}-${{ github.job }}\n          # if there's no cache hit, restore a cache by this prefix\n          restore-prefixes-first-match: nix-${{ runner.os }}\n          # collect garbage until the Nix store size (in bytes) is at most this number\n          # before trying to save a new cache\n          gc-max-store-size-linux: 5G\n\n      - name: Update inputs\n        run: nix/update-inputs.sh\n\n      - name: Commit\n        uses: stefanzweifel/git-auto-commit-action@v5\n        with:\n          commit_message: \"[gha] Nix: update inputs\"\n"
  },
  {
    "path": ".github/workflows/nix.yml",
    "content": "name: Build\n\non:\n  workflow_call:\n    inputs:\n      command:\n        required: true\n        type: string\n        description: Command to run\n    secrets:\n      CACHIX_AUTH_TOKEN:\n        required: false\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Nix\n        uses: nixbuild/nix-quick-install-action@v31\n        with:\n          nix_conf: |\n            keep-env-derivations = true\n            keep-outputs = true\n\n      - name: Restore and save Nix store\n        uses: nix-community/cache-nix-action@v6\n        with:\n          # restore and save a cache using this key (per job)\n          primary-key: nix-${{ runner.os }}-${{ github.job }}\n          # if there's no cache hit, restore a cache by this prefix\n          restore-prefixes-first-match: nix-${{ runner.os }}\n          # collect garbage until the Nix store size (in bytes) is at most this number\n          # before trying to save a new cache\n          gc-max-store-size-linux: 5G\n\n      - uses: cachix/cachix-action@v15\n        with:\n          name: hyprland\n          authToken: \"${{ secrets.CACHIX_AUTH_TOKEN }}\"\n\n      - run: ${{ inputs.command }}\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release artifacts\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n\njobs:\n  source-tarball:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n          submodules: recursive\n\n      - name: Populate git info in version.h.in\n        run: |\n          git fetch --tags --unshallow || true\n\n          COMMIT_HASH=$(git rev-parse HEAD)\n          BRANCH=\"${GITHUB_REF_NAME:-$(git rev-parse --abbrev-ref HEAD)}\"\n          COMMIT_MSG=$(git show -s --format=%s | sed 's/[&/]/\\\\&/g')\n          COMMIT_DATE=$(git show -s --format=%cd --date=local)\n          GIT_DIRTY=$(git diff-index --quiet HEAD -- && echo \"clean\" || echo \"dirty\")\n          GIT_TAG=$(git describe --tags --always || echo \"unknown\")\n          GIT_COMMITS=$(git rev-list --count HEAD)\n\n          echo \"Branch: $BRANCH\"\n          echo \"Tag: $GIT_TAG\"\n\n          sed -i \\\n            -e \"s|@GIT_COMMIT_HASH@|$COMMIT_HASH|\" \\\n            -e \"s|@GIT_BRANCH@|$BRANCH|\" \\\n            -e \"s|@GIT_COMMIT_MESSAGE@|$COMMIT_MSG|\" \\\n            -e \"s|@GIT_COMMIT_DATE@|$COMMIT_DATE|\" \\\n            -e \"s|@GIT_DIRTY@|$GIT_DIRTY|\" \\\n            -e \"s|@GIT_TAG@|$GIT_TAG|\" \\\n            -e \"s|@GIT_COMMITS@|$GIT_COMMITS|\" \\\n            src/version.h.in\n\n      - name: Create tarball with submodules\n        id: tar\n        run: |\n          mkdir hyprland-source; mv ./* ./hyprland-source || tar -czv --owner=0 --group=0 --no-same-owner --no-same-permissions -f source.tar.gz *\n\n      - id: whatrelease\n        name: Get latest release\n        uses: pozetroninc/github-action-get-latest-release@master\n        with:\n          owner: hyprwm\n          repo: Hyprland\n          excludes: prerelease, draft\n\n      - name: Upload to release\n        id: upload\n        uses: svenstaro/upload-release-action@v2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: source.tar.gz\n          asset_name: source-${{ steps.whatrelease.outputs.release }}.tar.gz\n          tag: ${{ steps.whatrelease.outputs.release }}\n          overwrite: true\n"
  },
  {
    "path": ".github/workflows/security-checks.yml",
    "content": "name: Security Checks\n\non: [push, pull_request]\n\njobs:\n  flawfinder:\n    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork\n    name: Flawfinder Checks\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Scan with Flawfinder\n        uses: david-a-wheeler/flawfinder@8e4a779ad59dbfaee5da586aa9210853b701959c\n        with:\n          arguments: '--sarif ./'\n          output: 'flawfinder_results.sarif'\n\n      - name: Upload analysis results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v2\n        with:\n          sarif_file: ${{github.workspace}}/flawfinder_results.sarif\n"
  },
  {
    "path": ".github/workflows/translation-ai-check.yml",
    "content": "name: AI Translation Check\n\non:\n  # pull_request_target:\n  #   types:\n  #     - opened\n  issue_comment:\n    types:\n      - created\n\npermissions:\n  contents: read\n  pull-requests: write\n  issues: write\n\njobs:\n  review:\n    name: Review Translation\n    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' ) }}\n    runs-on: ubuntu-latest\n    env:\n      OPENAI_MODEL: gpt-5-mini\n      SYSTEM_PROMPT: |\n        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.\n        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.\n\n      AI_PROMPT: Translation patch below.\n\n    steps:\n      - name: Checkout source code\n        uses: actions/checkout@v5\n\n      - uses: dorny/paths-filter@v3\n        id: changes\n        with:\n          filters: |\n            i18n:\n              - 'src/i18n/**'\n\n      - name: Stop if i18n not changed\n        if: steps.changes.outputs.i18n != 'true'\n        run: echo \"No i18n changes in this PR; skipping.\" && exit 0\n\n      - name: Determine PR number\n        id: pr\n        run: |\n          if [ \"${{ github.event_name }}\" = \"pull_request_target\" ]; then\n            echo \"number=${{ github.event.pull_request.number }}\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"number=${{ github.event.issue.number }}\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: Download combined PR diff\n        id: get_diff\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          PR_NUMBER: ${{ steps.pr.outputs.number }}\n        run: |\n          # Get the combined diff for the entire PR\n          curl -sSL \\\n            -H \"Authorization: token $GITHUB_TOKEN\" \\\n            -H \"Accept: application/vnd.github.v3.diff\" \\\n            \"https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER\" \\\n            -o pr.diff\n\n          # Compute character length\n          LEN=$(wc -c < pr.diff | tr -d ' ')\n          echo \"len=$LEN\" >> \"$GITHUB_OUTPUT\"\n          if [ \"$LEN\" -gt 25000 ]; then\n            echo \"too_long=true\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"too_long=false\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n          echo \"got diff:\"\n          cat pr.diff\n\n      - name: Comment when diff length exceeded\n        if: steps.get_diff.outputs.too_long == 'true'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          PR_NUMBER: ${{ steps.pr.outputs.number }}\n        run: |\n          jq -n --arg body \"Diff length exceeded, can't query API\" '{body: (\"AI translation check result:\\n\\n\" + $body)}' > body.json\n          curl -sS -X POST \\\n            -H \"Authorization: token $GITHUB_TOKEN\" \\\n            -H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments\" \\\n            --data @body.json\n\n      - name: Query OpenAI and post review\n        if: steps.get_diff.outputs.too_long == 'false'\n        env:\n          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\n          OPENAI_MODEL: ${{ env.OPENAI_MODEL }}\n          SYSTEM_PROMPT: ${{ env.SYSTEM_PROMPT }}\n          AI_PROMPT: ${{ env.AI_PROMPT }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          PR_NUMBER: ${{ steps.pr.outputs.number }}\n        run: |\n          # Prepare OpenAI chat request payload (embed diff safely)\n          jq -n \\\n            --arg model \"$OPENAI_MODEL\" \\\n            --arg sys \"$SYSTEM_PROMPT\" \\\n            --arg prompt \"$AI_PROMPT\" \\\n            --rawfile diff pr.diff \\\n            '{model:$model,\n              messages:[\n                {role:\"system\", content:$sys},\n                {role:\"user\", content: ($prompt + \"\\n\\n```diff\\n\" + $diff + \"\\n```\")}\n              ]\n            }' > payload.json\n\n          # Call OpenAI\n          curl -sS https://api.openai.com/v1/chat/completions \\\n            -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n            -H \"Content-Type: application/json\" \\\n            -d @payload.json > response.json\n\n          # Extract response text\n          COMMENT=$(jq -r '.choices[0].message.content // empty' response.json)\n          if [ -z \"$COMMENT\" ]; then\n            COMMENT=\"AI did not return a response.\"\n          fi\n\n          # If failed, add a note\n          ADDITIONAL_NOTE=\"\"\n          if [[ \"$COMMENT\" == *\"not ok\"* ]]; then\n            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.\")\n          fi\n\n          # Post the review as a PR comment\n          jq -n --arg body \"$COMMENT\" --arg note \"$ADDITIONAL_NOTE\" '{body: (\"AI translation check result:\\n\\n\" + $body + $note)}' > body.json\n          echo \"CURLing https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments\"\n          curl -sS -X POST \\\n            -H \"Authorization: token $GITHUB_TOKEN\" \\\n            -H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments\" \\\n            --data @body.json\n"
  },
  {
    "path": ".gitignore",
    "content": "CMakeLists.txt.user\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nTesting\ncmake_install.cmake\ninstall_manifest.txt\ncompile_commands.json\nCTestTestfile.cmake\nCPackConfig.cmake\nCPackSourceConfig.cmake\nhyprland.pc\n_deps\n\nbuild/\nresult*\n/.pre-commit-config.yaml\n/.vscode/\n/.idea/\n.envrc\n.cache\n.direnv\n/.cmake/\n/.worktree/\n\n*.o\nprotocols/*.c*\nprotocols/*.h*\n.ccls-cache\n*.so\nsrc/render/shaders/*.inc\nsrc/render/shaders/Shaders.hpp\n\nhyprctl/hyprctl\nhyprctl/hw-protocols/*.c*\nhyprctl/hw-protocols/*.h*\n\ngmon.out\n*.out\n*.tar.gz\n\nPKGBUILD\n\nsrc/version.h\nhyprpm/Makefile\nhyprctl/Makefile\nexample/hyprland.desktop\n\n**/.#*.*\n**/#*.*#\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"subprojects/hyprland-protocols\"]\n\tpath = subprojects/hyprland-protocols\n\turl = https://github.com/hyprwm/hyprland-protocols\n[submodule \"subprojects/udis86\"]\n\tpath = subprojects/udis86\n\turl = https://github.com/canihavesomecoffee/udis86\n[submodule \"subprojects/tracy\"]\n\tpath = subprojects/tracy\n\turl = https://github.com/wolfpld/tracy\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.30)\n\n# Get version\nfile(READ \"${CMAKE_SOURCE_DIR}/VERSION\" VER_RAW)\nstring(STRIP ${VER_RAW} VER)\n\nproject(\n  Hyprland\n  DESCRIPTION \"A Modern C++ Wayland Compositor\"\n  VERSION ${VER})\n\ninclude(CTest)\ninclude(CheckIncludeFile)\ninclude(GNUInstallDirs)\n\nset(HYPRLAND_VERSION ${VER})\nset(PREFIX ${CMAKE_INSTALL_PREFIX})\nset(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})\nset(BINDIR ${CMAKE_INSTALL_BINDIR})\n\nset(CMAKE_MESSAGE_LOG_LEVEL \"STATUS\")\n\nmessage(STATUS \"Gathering git info\")\n\n# Make shader files includable\nexecute_process(COMMAND ./scripts/generateShaderIncludes.sh\n                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n                RESULT_VARIABLE HYPR_SHADER_GEN_RESULT)\nif(NOT HYPR_SHADER_GEN_RESULT EQUAL 0)\n  message(\n    FATAL_ERROR\n      \"Failed to generate shader includes (scripts/generateShaderIncludes.sh), exit code: ${HYPR_SHADER_GEN_RESULT}\"\n  )\nendif()\n\nfind_package(PkgConfig REQUIRED)\n\n# Try to find canihavesomecoffee's udis86 using pkgconfig vmd/udis86 does not\n# provide a .pc file and won't be detected this way\npkg_check_modules(udis_dep IMPORTED_TARGET udis86>=1.7.2)\n\n# Find non-pkgconfig udis86, otherwise fallback to subproject\nif(NOT udis_dep_FOUND)\n  find_library(udis_nopc udis86)\n  if(NOT(\"${udis_nopc}\" MATCHES \"udis_nopc-NOTFOUND\"))\n    message(STATUS \"Found udis86 at ${udis_nopc}\")\n  else()\n    add_subdirectory(\"subprojects/udis86\")\n    include_directories(\"subprojects/udis86\")\n    message(STATUS \"udis86 dependency not found, falling back to subproject\")\n  endif()\nendif()\n\nfind_library(librt rt)\nif(\"${librt}\" MATCHES \"librt-NOTFOUND\")\n  unset(LIBRT)\nelse()\n  set(LIBRT rt)\nendif()\n\nif(CMAKE_BUILD_TYPE)\n  string(TOLOWER ${CMAKE_BUILD_TYPE} BUILDTYPE_LOWER)\n  if(BUILDTYPE_LOWER STREQUAL \"release\")\n    # Pass.\n  elseif(BUILDTYPE_LOWER STREQUAL \"debug\")\n    # Pass.\n  elseif(BUILDTYPE_LOWER STREQUAL \"relwithdebinfo\")\n    set(BUILDTYPE_LOWER \"debugoptimized\")\n  elseif(BUILDTYPE_LOWER STREQUAL \"minsizerel\")\n    set(BUILDTYPE_LOWER \"minsize\")\n  elseif(BUILDTYPE_LOWER STREQUAL \"none\")\n    set(BUILDTYPE_LOWER \"plain\")\n  else()\n    set(BUILDTYPE_LOWER \"release\")\n  endif()\nelse()\n  set(BUILDTYPE_LOWER \"release\")\nendif()\n\npkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)\nmessage(STATUS \"Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}\")\npkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)\nmessage(\n  STATUS \"Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}\")\n\nif(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)\n  message(STATUS \"Configuring Hyprland in Debug with CMake\")\n  add_compile_definitions(HYPRLAND_DEBUG)\n  set(BUILD_TESTING ON)\nelse()\n  add_compile_options(-O3)\n  message(STATUS \"Configuring Hyprland in Release with CMake\")\n  set(BUILD_TESTING OFF)\nendif()\n\nadd_compile_definitions(HYPRLAND_VERSION=\"${HYPRLAND_VERSION}\")\n\ninclude_directories(. \"src/\" \"protocols/\")\n\nset(CMAKE_CXX_STANDARD 26)\nset(CXX_STANDARD_REQUIRED ON)\nadd_compile_options(\n  -Wall\n  -Wextra\n  -Wpedantic\n  -Wno-unused-parameter\n  -Wno-unused-value\n  -Wno-missing-field-initializers\n  -Wno-gnu-zero-variadic-macro-arguments\n  -Wno-narrowing\n  -Wno-pointer-arith\n  -Wno-clobbered\n  -frtti\n  -fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)\n\n# disable lto as it may break plugins\nadd_compile_options(-fno-lto)\n\nset(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)\nset(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)\n\nmessage(STATUS \"Checking deps...\")\n\nfind_package(Threads REQUIRED)\n\nset(GLES_VERSION \"GLES3\")\nfind_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})\nfind_package(glslang CONFIG REQUIRED)\n\nset(AQUAMARINE_MINIMUM_VERSION 0.9.3)\nset(HYPRLANG_MINIMUM_VERSION 0.6.7)\nset(HYPRCURSOR_MINIMUM_VERSION 0.1.7)\nset(HYPRUTILS_MINIMUM_VERSION 0.11.0)\nset(HYPRGRAPHICS_MINIMUM_VERSION 0.1.6)\n\npkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION})\npkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=${HYPRLANG_MINIMUM_VERSION})\npkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=${HYPRCURSOR_MINIMUM_VERSION})\npkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=${HYPRUTILS_MINIMUM_VERSION})\npkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=${HYPRGRAPHICS_MINIMUM_VERSION})\n\nstring(REPLACE \".\" \";\" AQ_VERSION_LIST ${aquamarine_dep_VERSION})\nlist(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR)\nlist(GET AQ_VERSION_LIST 1 AQ_VERSION_MINOR)\nlist(GET AQ_VERSION_LIST 2 AQ_VERSION_PATCH)\n\nset(AQUAMARINE_VERSION \"${aquamarine_dep_VERSION}\")\nset(AQUAMARINE_VERSION_MAJOR \"${AQ_VERSION_MAJOR}\")\nset(AQUAMARINE_VERSION_MINOR \"${AQ_VERSION_MINOR}\")\nset(AQUAMARINE_VERSION_PATCH \"${AQ_VERSION_PATCH}\")\nset(HYPRLANG_VERSION \"${hyprlang_dep_VERSION}\")\nset(HYPRUTILS_VERSION \"${hyprutils_dep_VERSION}\")\nset(HYPRCURSOR_VERSION \"${hyprcursor_dep_VERSION}\")\nset(HYPRGRAPHICS_VERSION \"${hyprgraphics_dep_VERSION}\")\n\n\nfind_package(Git QUIET)\n\n# Populate variables with env vars if present\nset(GIT_COMMIT_HASH \"$ENV{GIT_COMMIT_HASH}\")\nif(NOT GIT_COMMIT_HASH)\n  set(GIT_COMMIT_HASH \"unknown\")\nendif()\n\nset(GIT_BRANCH \"$ENV{GIT_BRANCH}\")\nif(NOT GIT_BRANCH)\n  set(GIT_BRANCH \"unknown\")\nendif()\n\nset(GIT_COMMIT_MESSAGE \"$ENV{GIT_COMMIT_MESSAGE}\")\nif(NOT GIT_COMMIT_MESSAGE)\n  set(GIT_COMMIT_MESSAGE \"unknown\")\nendif()\n\nset(GIT_COMMIT_DATE \"$ENV{GIT_COMMIT_DATE}\")\nif(NOT GIT_COMMIT_DATE)\n  set(GIT_COMMIT_DATE \"unknown\")\nendif()\n\nset(GIT_DIRTY \"$ENV{GIT_DIRTY}\")\nif(NOT GIT_DIRTY)\n  set(GIT_DIRTY \"unknown\")\nendif()\n\nset(GIT_TAG \"$ENV{GIT_TAG}\")\nif(NOT GIT_TAG)\n  set(GIT_TAG \"unknown\")\nendif()\n\nset(GIT_COMMITS \"$ENV{GIT_COMMITS}\")\nif(NOT GIT_COMMITS)\n  set(GIT_COMMITS \"0\")\nendif()\n\nif(Git_FOUND)\n  execute_process(\n    COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel\n    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n    OUTPUT_VARIABLE GIT_TOPLEVEL\n    OUTPUT_STRIP_TRAILING_WHITESPACE\n    ERROR_QUIET\n    RESULT_VARIABLE GIT_TOPLEVEL_RESULT\n  )\n\n  if(GIT_TOPLEVEL_RESULT EQUAL 0)\n    message(STATUS \"Detected git repository root: ${GIT_TOPLEVEL}\")\n\n    execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE)\n    execute_process(COMMAND ${GIT_EXECUTABLE} branch --show-current\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE)\n    execute_process(COMMAND sh \"-c\" \"${GIT_EXECUTABLE} show -s --format=%s --no-show-signature | sed \\\"s/\\\\\\\"/\\'/g\\\"\"\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      OUTPUT_VARIABLE GIT_COMMIT_MESSAGE OUTPUT_STRIP_TRAILING_WHITESPACE)\n    execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local --no-show-signature\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      OUTPUT_VARIABLE GIT_COMMIT_DATE OUTPUT_STRIP_TRAILING_WHITESPACE)\n    execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --quiet HEAD --\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      RESULT_VARIABLE GIT_DIRTY_RESULT)\n    if(NOT GIT_DIRTY_RESULT EQUAL 0)\n      set(GIT_DIRTY \"dirty\")\n    else()\n      set(GIT_DIRTY \"clean\")\n    endif()\n    execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE)\n    execute_process(COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD\n      WORKING_DIRECTORY ${GIT_TOPLEVEL}\n      OUTPUT_VARIABLE GIT_COMMITS OUTPUT_STRIP_TRAILING_WHITESPACE)\n  else()\n    message(WARNING \"No Git repository detected in ${CMAKE_SOURCE_DIR}\")\n  endif()\nendif()\n\nconfigure_file(\n    ${CMAKE_SOURCE_DIR}/src/version.h.in\n    ${CMAKE_SOURCE_DIR}/src/version.h\n    @ONLY\n)\n\nset_source_files_properties(${CMAKE_SOURCE_DIR}/src/version.h PROPERTIES GENERATED TRUE)\n\nset(XKBCOMMON_MINIMUM_VERSION 1.11.0)\nset(WAYLAND_SERVER_MINIMUM_VERSION 1.22.91)\nset(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45)\nset(LIBINPUT_MINIMUM_VERSION 1.28)\n\npkg_check_modules(\n  deps\n  REQUIRED\n  IMPORTED_TARGET GLOBAL\n  xkbcommon>=${XKBCOMMON_MINIMUM_VERSION}\n  uuid\n  wayland-server>=${WAYLAND_SERVER_MINIMUM_VERSION}\n  wayland-protocols>=${WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION}\n  cairo\n  pango\n  pangocairo\n  pixman-1\n  xcursor\n  libdrm\n  libinput>=${LIBINPUT_MINIMUM_VERSION}\n  gbm\n  gio-2.0\n  re2\n  muparser\n  lcms2)\n\nfind_package(hyprwayland-scanner 0.3.10 REQUIRED)\n\nfile(GLOB_RECURSE SRCFILES \"src/*.cpp\")\nget_filename_component(FULL_MAIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ABSOLUTE)\nlist(REMOVE_ITEM SRCFILES \"${FULL_MAIN_PATH}\")\n\nset(TRACY_CPP_FILES \"\")\nif(USE_TRACY)\n  set(TRACY_CPP_FILES \"subprojects/tracy/public/TracyClient.cpp\")\n  message(STATUS \"Tracy enabled, TRACY_CPP_FILES: \" ${TRACY_CPP_FILES})\nendif()\n\nadd_library(hyprland_lib STATIC ${SRCFILES})\nadd_executable(Hyprland src/main.cpp ${TRACY_CPP_FILES})\ntarget_link_libraries(Hyprland hyprland_lib)\n\ntarget_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS})\ntarget_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS})\n\nset(USE_GPROF OFF)\n\nif(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)\n  message(STATUS \"Setting debug flags\")\n\n  if(WITH_ASAN)\n    message(STATUS \"Enabling ASan\")\n\n    target_link_libraries(hyprland_lib PUBLIC asan)\n    target_compile_options(hyprland_lib PUBLIC -fsanitize=address)\n  endif()\n\n  add_compile_options(-fno-pie -fno-builtin)\n  add_link_options(-no-pie -fno-builtin)\n  if(USE_GPROF)\n    add_compile_options(-pg)\n    add_link_options(-pg)\n  endif()\nendif()\n\nif(USE_TRACY)\n  message(STATUS \"Tracy is turned on\")\n\n  option(TRACY_ENABLE \"\" ON)\n  option(TRACY_ON_DEMAND \"\" ON)\n  add_subdirectory(subprojects/tracy)\n\n  add_compile_options(-fno-omit-frame-pointer)\n\n  target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient)\n\n  if(USE_TRACY_GPU)\n    message(STATUS \"Tracy GPU Profiling is turned on\")\n    add_compile_definitions(USE_TRACY_GPU)\n  endif()\nendif()\n\nif(BUILT_WITH_NIX)\n  add_compile_definitions(BUILT_WITH_NIX)\nendif()\n\ncheck_include_file(\"execinfo.h\" EXECINFOH)\nif(EXECINFOH)\n  message(STATUS \"Configuration supports execinfo\")\n  add_compile_definitions(HAS_EXECINFO)\nendif()\n\ninclude(CheckLibraryExists)\ncheck_library_exists(execinfo backtrace \"\" HAVE_LIBEXECINFO)\nif(HAVE_LIBEXECINFO)\n  target_link_libraries(hyprland_lib PUBLIC execinfo)\nendif()\n\ncheck_include_file(\"sys/timerfd.h\" HAS_TIMERFD)\npkg_check_modules(epoll IMPORTED_TARGET epoll-shim)\nif(NOT HAS_TIMERFD AND epoll_FOUND)\n  target_link_libraries(hyprland_lib PUBLIC PkgConfig::epoll)\nendif()\n\ncheck_include_file(\"sys/inotify.h\" HAS_INOTIFY)\npkg_check_modules(inotify IMPORTED_TARGET libinotify)\nif(NOT HAS_INOTIFY AND inotify_FOUND)\n  target_link_libraries(hyprland_lib PUBLIC PkgConfig::inotify)\nendif()\n\nif(NO_XWAYLAND)\n  message(STATUS \"Using the NO_XWAYLAND flag, disabling XWayland!\")\n  add_compile_definitions(NO_XWAYLAND)\nelse()\n  message(STATUS \"XWAYLAND Enabled (NO_XWAYLAND not defined) checking deps...\")\n  set(XWAYLAND_DEPENDENCIES\n    xcb\n    xcb-render\n    xcb-xfixes\n    xcb-icccm\n    xcb-composite\n    xcb-res\n    xcb-errors)\n\n  pkg_check_modules(\n    xdeps\n    REQUIRED\n    IMPORTED_TARGET\n    ${XWAYLAND_DEPENDENCIES})\n\n  string(JOIN \", \" PKGCONFIG_XWAYLAND_DEPENDENCIES ${XWAYLAND_DEPENDENCIES})\n  string(PREPEND PKGCONFIG_XWAYLAND_DEPENDENCIES \", \")\n\n  target_link_libraries(hyprland_lib PUBLIC PkgConfig::xdeps)\nendif()\n\nconfigure_file(hyprland.pc.in hyprland.pc @ONLY)\n\nif(NO_SYSTEMD)\n  message(STATUS \"SYSTEMD support is disabled...\")\nelse()\n  message(STATUS \"SYSTEMD support is requested (NO_SYSTEMD not defined)...\")\n  add_compile_definitions(USES_SYSTEMD)\n\n  # session file -uwsm\n  if(NO_UWSM)\n    message(STATUS \"UWSM support is disabled...\")\n  else()\n    message(STATUS \"UWSM support is enabled (NO_UWSM not defined)...\")\n    install(FILES ${CMAKE_SOURCE_DIR}/systemd/hyprland-uwsm.desktop\n            DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions)\n  endif()\nendif()\n\nset(CPACK_PROJECT_NAME ${PROJECT_NAME})\nset(CPACK_PROJECT_VERSION ${PROJECT_VERSION})\ninclude(CPack)\n\nif(CMAKE_DISABLE_PRECOMPILE_HEADERS)\n  message(STATUS \"Not using precompiled headers\")\nelse()\n  message(STATUS \"Setting precompiled headers\")\n  target_precompile_headers(hyprland_lib PRIVATE\n                            $<$<COMPILE_LANGUAGE:CXX>:src/pch/pch.hpp>)\nendif()\n\nmessage(STATUS \"Setting link libraries\")\n\ntarget_link_libraries(\n  hyprland_lib\n  PUBLIC\n  PkgConfig::aquamarine_dep\n  PkgConfig::hyprlang_dep\n  PkgConfig::hyprutils_dep\n  PkgConfig::hyprcursor_dep\n  PkgConfig::hyprgraphics_dep\n  PkgConfig::deps\n)\n\ntarget_link_libraries(\n  Hyprland\n  ${LIBRT}\n  hyprland_lib)\nif(udis_dep_FOUND)\n  target_link_libraries(hyprland_lib PUBLIC PkgConfig::udis_dep)\nelseif(NOT(\"${udis_nopc}\" MATCHES \"udis_nopc-NOTFOUND\"))\n  target_link_libraries(hyprland_lib PUBLIC ${udis_nopc})\nelse()\n  target_link_libraries(hyprland_lib PUBLIC libudis86)\nendif()\n\n# used by `make installheaders`, to ensure the headers are generated\nadd_custom_target(generate-protocol-headers)\nset(PROTOCOL_SOURCES \"\")\n\nfunction(protocolnew protoPath protoName external)\n  if(external)\n    set(path ${protoPath})\n  else()\n    set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})\n  endif()\n  add_custom_command(\n    OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp\n           ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp\n    COMMAND hyprwayland-scanner ${path}/${protoName}.xml\n            ${CMAKE_SOURCE_DIR}/protocols/\n    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})\n  target_sources(hyprland_lib PRIVATE protocols/${protoName}.cpp\n                                  protocols/${protoName}.hpp)\n  target_sources(generate-protocol-headers\n                 PRIVATE ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp)\n\n  list(APPEND PROTOCOL_SOURCES \"${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp\")\n  set(PROTOCOL_SOURCES \"${PROTOCOL_SOURCES}\" PARENT_SCOPE)\n  list(APPEND PROTOCOL_SOURCES \"${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp\")\n  set(PROTOCOL_SOURCES \"${PROTOCOL_SOURCES}\" PARENT_SCOPE)\nendfunction()\nfunction(protocolWayland)\n  add_custom_command(\n    OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp\n           ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp\n    COMMAND\n      hyprwayland-scanner --wayland-enums\n      ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/\n    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})\n  target_sources(hyprland_lib PRIVATE protocols/wayland.cpp protocols/wayland.hpp)\n  target_sources(generate-protocol-headers\n                 PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp)\n\n  list(APPEND PROTOCOL_SOURCES \"${CMAKE_SOURCE_DIR}/protocols/wayland.hpp\")\n  set(PROTOCOL_SOURCES \"${PROTOCOL_SOURCES}\" PARENT_SCOPE)\n  list(APPEND PROTOCOL_SOURCES \"${CMAKE_SOURCE_DIR}/protocols/wayland.cpp\")\n  set(PROTOCOL_SOURCES \"${PROTOCOL_SOURCES}\" PARENT_SCOPE)\nendfunction()\n\nif(TARGET OpenGL::GL)\n  target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)\nelse()\n  target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)\nendif()\n\npkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)\nif(hyprland_protocols_dep_FOUND)\n  pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir)\n  message(STATUS \"hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}\")\nelse()\n  set(HYPRLAND_PROTOCOLS \"subprojects/hyprland-protocols\")\n  message(STATUS \"hyprland-protocols subproject set to ${HYPRLAND_PROTOCOLS}\")\nendif()\n\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-global-shortcuts-v1\"\n            true)\nprotocolnew(\"unstable/text-input\" \"text-input-unstable-v1\" false)\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-toplevel-export-v1\"\n            true)\nprotocolnew(\"protocols\" \"wlr-screencopy-unstable-v1\" true)\nprotocolnew(\"protocols\" \"wlr-gamma-control-unstable-v1\" true)\nprotocolnew(\"protocols\" \"wlr-foreign-toplevel-management-unstable-v1\" true)\nprotocolnew(\"protocols\" \"wlr-output-power-management-unstable-v1\" true)\nprotocolnew(\"protocols\" \"virtual-keyboard-unstable-v1\" true)\nprotocolnew(\"protocols\" \"wlr-virtual-pointer-unstable-v1\" true)\nprotocolnew(\"protocols\" \"input-method-unstable-v2\" true)\nprotocolnew(\"protocols\" \"wlr-output-management-unstable-v1\" true)\nprotocolnew(\"protocols\" \"kde-server-decoration\" true)\nprotocolnew(\"protocols\" \"wlr-data-control-unstable-v1\" true)\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-focus-grab-v1\" true)\nprotocolnew(\"protocols\" \"wlr-layer-shell-unstable-v1\" true)\nprotocolnew(\"protocols\" \"wayland-drm\" true)\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-ctm-control-v1\" true)\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-surface-v1\" true)\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-lock-notify-v1\" true)\nprotocolnew(\"${HYPRLAND_PROTOCOLS}/protocols\" \"hyprland-toplevel-mapping-v1\"\n            true)\n\nprotocolnew(\"staging/tearing-control\" \"tearing-control-v1\" false)\nprotocolnew(\"staging/fractional-scale\" \"fractional-scale-v1\" false)\nprotocolnew(\"unstable/xdg-output\" \"xdg-output-unstable-v1\" false)\nprotocolnew(\"staging/cursor-shape\" \"cursor-shape-v1\" false)\nprotocolnew(\"unstable/idle-inhibit\" \"idle-inhibit-unstable-v1\" false)\nprotocolnew(\"unstable/relative-pointer\" \"relative-pointer-unstable-v1\" false)\nprotocolnew(\"unstable/xdg-decoration\" \"xdg-decoration-unstable-v1\" false)\nprotocolnew(\"staging/alpha-modifier\" \"alpha-modifier-v1\" false)\nprotocolnew(\"staging/ext-foreign-toplevel-list\" \"ext-foreign-toplevel-list-v1\"\n            false)\nprotocolnew(\"unstable/pointer-gestures\" \"pointer-gestures-unstable-v1\" false)\nprotocolnew(\"unstable/keyboard-shortcuts-inhibit\"\n            \"keyboard-shortcuts-inhibit-unstable-v1\" false)\nprotocolnew(\"unstable/text-input\" \"text-input-unstable-v3\" false)\nprotocolnew(\"unstable/pointer-constraints\" \"pointer-constraints-unstable-v1\"\n            false)\nprotocolnew(\"staging/xdg-activation\" \"xdg-activation-v1\" false)\nprotocolnew(\"staging/ext-idle-notify\" \"ext-idle-notify-v1\" false)\nprotocolnew(\"staging/ext-session-lock\" \"ext-session-lock-v1\" false)\nprotocolnew(\"stable/tablet\" \"tablet-v2\" false)\nprotocolnew(\"stable/presentation-time\" \"presentation-time\" false)\nprotocolnew(\"stable/xdg-shell\" \"xdg-shell\" false)\nprotocolnew(\"unstable/primary-selection\" \"primary-selection-unstable-v1\" false)\nprotocolnew(\"staging/xwayland-shell\" \"xwayland-shell-v1\" false)\nprotocolnew(\"stable/viewporter\" \"viewporter\" false)\nprotocolnew(\"stable/linux-dmabuf\" \"linux-dmabuf-v1\" false)\nprotocolnew(\"staging/drm-lease\" \"drm-lease-v1\" false)\nprotocolnew(\"staging/linux-drm-syncobj\" \"linux-drm-syncobj-v1\" false)\nprotocolnew(\"staging/xdg-dialog\" \"xdg-dialog-v1\" false)\nprotocolnew(\"staging/single-pixel-buffer\" \"single-pixel-buffer-v1\" false)\nprotocolnew(\"staging/security-context\" \"security-context-v1\" false)\nprotocolnew(\"staging/content-type\" \"content-type-v1\" false)\nprotocolnew(\"staging/color-management\" \"color-management-v1\" false)\nprotocolnew(\"staging/xdg-toplevel-tag\" \"xdg-toplevel-tag-v1\" false)\nprotocolnew(\"staging/xdg-system-bell\" \"xdg-system-bell-v1\" false)\nprotocolnew(\"staging/ext-workspace\" \"ext-workspace-v1\" false)\nprotocolnew(\"staging/ext-data-control\" \"ext-data-control-v1\" false)\nprotocolnew(\"staging/pointer-warp\" \"pointer-warp-v1\" false)\nprotocolnew(\"staging/fifo\" \"fifo-v1\" false)\nprotocolnew(\"staging/commit-timing\" \"commit-timing-v1\" false)\nprotocolnew(\"staging/ext-image-capture-source\" \"ext-image-capture-source-v1\" false)\nprotocolnew(\"staging/ext-image-copy-capture\" \"ext-image-copy-capture-v1\" false)\n\nprotocolwayland()\n\n# tools\nadd_subdirectory(hyprctl)\nadd_subdirectory(start)\n\nif(NO_HYPRPM)\n  message(STATUS \"hyprpm is disabled\")\nelse()\n  add_subdirectory(hyprpm)\n  message(STATUS \"hyprpm is enabled (NO_HYPRPM not defined)\")\nendif()\n\n# binary and symlink\ninstall(TARGETS Hyprland)\n\ninstall(\n  CODE \"execute_process( \\\n        COMMAND ${CMAKE_COMMAND} -E create_symlink \\\n        ${CMAKE_INSTALL_FULL_BINDIR}/Hyprland \\\n        \\\"\\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\\\" \\\n        )\")\n# session file\nconfigure_file(\n    ${CMAKE_SOURCE_DIR}/example/hyprland.desktop.in\n    ${CMAKE_SOURCE_DIR}/example/hyprland.desktop\n    @ONLY\n)\ninstall(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions)\n\n# allow Hyprland to find assets\nadd_compile_definitions(DATAROOTDIR=\"${CMAKE_INSTALL_FULL_DATAROOTDIR}\")\n\n# installable assets\nfile(GLOB_RECURSE INSTALLABLE_ASSETS \"assets/install/*\")\ninstall(FILES ${INSTALLABLE_ASSETS}\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)\n\n# default config\ninstall(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.conf\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)\n\n# portal config\ninstall(FILES ${CMAKE_SOURCE_DIR}/assets/hyprland-portals.conf\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/xdg-desktop-portal)\n\n# man pages\nfile(GLOB_RECURSE MANPAGES \"docs/*.1\")\ninstall(FILES ${MANPAGES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)\n\n# pkgconfig entry\ninstall(FILES ${CMAKE_BINARY_DIR}/hyprland.pc\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)\n\n# protocol headers\nset(HEADERS_PROTO \"${CMAKE_CURRENT_SOURCE_DIR}/protocols\")\ninstall(\n  DIRECTORY ${HEADERS_PROTO}\n  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hyprland\n  FILES_MATCHING\n  PATTERN \"*.h*\")\n\n# hyprland headers\nset(HEADERS_SRC \"${CMAKE_CURRENT_SOURCE_DIR}/src\")\ninstall(\n  DIRECTORY ${HEADERS_SRC}\n  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hyprland\n  FILES_MATCHING\n  PATTERN \"*.h\"\n  PATTERN \"*.hpp\"\n  PATTERN \"*.inc\")\n\nif(BUILD_TESTING OR WITH_TESTS)\n  message(STATUS \"Building tests\")\n\n  # hyprtester\n  add_subdirectory(hyprtester)\n\n  # GTest\n  find_package(GTest CONFIG REQUIRED)\n  include(GoogleTest)\n  file(GLOB_RECURSE TESTFILES \"tests/*.cpp\")\n  add_executable(hyprland_gtests ${TESTFILES})\n  target_compile_options(hyprland_gtests PRIVATE --coverage)\n  target_link_options(hyprland_gtests PRIVATE --coverage)\n  target_include_directories(\n    hyprland_gtests\n    PUBLIC \"./include\"\n    PRIVATE \"./src\" \"./src/include\" \"./protocols\" \"${CMAKE_BINARY_DIR}\")\n\n  target_link_libraries(hyprland_gtests hyprland_lib GTest::gtest_main)\n\n  gtest_discover_tests(hyprland_gtests)\n\n  # Enable coverage in main hyprland lib\n  target_compile_options(hyprland_lib PRIVATE --coverage)\n  target_link_options(hyprland_lib PRIVATE --coverage)\n  target_link_libraries(hyprland_lib PUBLIC gcov)\n\n  # Enable coverage in hyprland exe\n  target_compile_options(Hyprland PRIVATE --coverage)\n  target_link_options(Hyprland PRIVATE --coverage)\n  target_link_libraries(Hyprland gcov)\nendif()\n\nif(BUILD_TESTING)\n  message(STATUS \"Testing is enabled\")\n\n  enable_testing()\n  add_custom_target(tests)\n\n  add_dependencies(tests hyprland_gtests)\n\nelse()\n  message(STATUS \"Testing is disabled\")\nendif()\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Goal\n\nOur goal is to provide a space where it is safe for everyone to contribute to,\nand get support for, open-source software in a respectful and cooperative\nmanner.\n\nWe value all contributions and want to make this organization and its\nsurrounding community a place for everyone.\n\nAs members, contributors, and everyone else who may participate in the\ndevelopment, we strive to keep the entire experience civil.\n\n## Standards\n\nOur community standards exist in order to make sure everyone feels comfortable\ncontributing to the project(s) together.\n\nOur standards are:\n - Do not harass, attack, or in any other way discriminate against anyone, including\nfor their protected traits, including, but not limited to, sex, religion, race,\nappearance, gender, identity, nationality, sexuality, etc.\n - Do not go off-topic, do not post spam.\n - Treat everyone with respect.\n\nExamples of breaking each rule respectively include:\n - Harassment, bullying or inappropriate jokes about another person.\n - Posting distasteful imagery, trolling, or posting things unrelated to the topic at hand.\n - Treating someone as worse because of their lack of understanding of an issue.\n\n## Enforcement\n\nEnforcement of this CoC is done by the members of the hyprwm organization.\n\nWe, as the organization, will strive our best to keep this community civil and\nfollowing the standards outlined above.\n\n### Reporting incidents\n\nIf you believe an incident of breaking our standards has occurred, but nobody has\ntaken appropriate action, you can privately contact the people responsible for dealing\nwith such incidents in multiple ways:\n\n***E-Mail***\n - `vaxry[at]vaxry.net`\n - `mihai[at]fufexan.net`\n\n***Discord***\n - `@vaxry`\n - `@fufexan`\n\n***Matrix***\n - `@vaxry:matrix.vaxry.net`\n - `@fufexan:matrix.org`\n \nWe, as members, guarantee your privacy and will not share those reports with anyone.\n\n## Enforcement Strategy\n\nDepending on the severity of the infraction, any action from the list below may be applied.\nPlease keep in mind cases are reviewed on a per-case basis and members are the ultimate\ndeciding factor in the type of punishment.\n\nIf the matter would benefit from an outside opinion, a member might reach for more opinions\nfrom people unrelated to the organization, however, the final decision regarding the action\nto be taken is still up to the member.\n\nFor example, if the matter at hand regards a representative of a marginalized group or minority,\nthe member might ask for a first-hand opinion from another representative of such group.\n\n### Correction/Edit\n\nIf your message is found to be misleading or poorly worded, a member might\nedit your message.\n\n### Warning/Deletion\n\nIf your message is found inappropriate, a member might give you a public or private warning,\nand/or delete your message.\n\n### Mute\n\nIf your message is disruptive, or you have been repeatedly violating the standards,\na member might mute (or temporarily ban) you.\n\n### Ban\n\nIf your message is hateful, very disruptive, or other, less serious infractions are repeated\nignoring previous punishments, a member might ban you permanently.\n\n## Scope\n\nThis CoC shall apply to all projects ran under the `hyprwm` organization and all _official_ communities\noutside of GitHub.\n\nHowever, it is worth noting that official communities outside of GitHub might have their own,\nadditional sets of rules.\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2022-2026, vaxerski\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "PREFIX = /usr/local\n\nstub:\n\t@echo \"Do not run $(MAKE) directly without any arguments. Please refer to the wiki on how to compile Hyprland.\"\n\nrelease:\n\tcmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build\n\tcmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`\n\ndebug:\n\tcmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DTESTS=true -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build\n\tcmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`\n\nnopch:\n\tcmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -S . -B ./build\n\tcmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`\n\nclear:\n\trm -rf build\n\trm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp\n\trm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp\n\nall:\n\t$(MAKE) clear\n\t$(MAKE) release\n\ninstall:\n\tcmake --install ./build\n\nuninstall:\n\txargs rm < ./build/install_manifest.txt\n\npluginenv:\n\t@echo -en \"$(MAKE) pluginenv has been deprecated.\\nPlease run $(MAKE) all && sudo $(MAKE) installheaders\\n\"\n\t@exit 1\n\ninstallheaders:\n\t@if [ ! -f ./src/version.h ]; then echo -en \"You need to run $(MAKE) all first.\\n\" && exit 1; fi\n\n\t# remove previous headers from hyprpm's dir\n\trm -fr ${PREFIX}/include/hyprland\n\tmkdir -p ${PREFIX}/include/hyprland\n\tmkdir -p ${PREFIX}/include/hyprland/protocols\n\tmkdir -p ${PREFIX}/share/pkgconfig\n\n\tcmake --build ./build --config Release --target generate-protocol-headers\n\n\tfind src -type f \\( -name '*.hpp' -o -name '*.h' -o -name '*.inc' \\) -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland\n\tcp ./protocols/*.h* ${PREFIX}/include/hyprland/protocols\n\tcp ./build/hyprland.pc ${PREFIX}/share/pkgconfig\n\tif [ -d /usr/share/pkgconfig ]; then cp ./build/hyprland.pc /usr/share/pkgconfig 2>/dev/null || true; fi\n\n\tchmod -R 755 ${PREFIX}/include/hyprland\n\tchmod 755 ${PREFIX}/share/pkgconfig\n\nman:\n\tpandoc ./docs/Hyprland.1.rst \\\n\t\t--standalone \\\n\t\t--variable=header:\"Hyprland User Manual\" \\\n\t\t--variable=date:\"${DATE}\" \\\n\t\t--variable=section:1 \\\n\t\t--from rst \\\n\t\t--to man > ./docs/Hyprland.1\n\n\tpandoc ./docs/hyprctl.1.rst \\\n\t\t--standalone \\\n\t\t--variable=header:\"hyprctl User Manual\" \\\n\t\t--variable=date:\"${DATE}\" \\\n\t\t--variable=section:1 \\\n\t\t--from rst \\\n\t\t--to man > ./docs/hyprctl.1\n\nasan:\n\t@echo -en \"!!WARNING!!\\nOnly run this in the TTY.\\n\"\n\t@pidof Hyprland > /dev/null && echo -ne \"Refusing to run with Hyprland running.\\n\" || echo \"\"\n\t@pidof Hyprland > /dev/null && exit 1 || echo \"\"\n\n\trm -rf ./wayland\n\t#git reset --hard\n\n\t@echo -en \"If you want to apply a patch, input its path (leave empty for none):\\n\"\n\t@read patchvar; \\\n\t if [ -n \"$$patchvar\" ]; then patch -p1 < \"$$patchvar\" || echo \"\"; else echo \"No patch specified\"; fi\n\n\tgit clone --recursive https://gitlab.freedesktop.org/wayland/wayland\n\tcd wayland && patch -p1 < ../scripts/waylandStatic.diff && meson setup build --buildtype=debug -Db_sanitize=address -Ddocumentation=false && ninja -C build && cd ..\n\tcp ./wayland/build/src/libwayland-server.a .\n\t@echo \"Wayland done\"\n\n\tpatch -p1 < ./scripts/hyprlandStaticAsan.diff\n\tcmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build\n\tcmake --build ./build --config Debug --target all\n\t@echo \"Hyprland done\"\n\n\tASAN_OPTIONS=\"detect_odr_violation=0,log_path=asan.log\" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf\n\ntest:\n\t$(MAKE) debug\n\t./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so\n"
  },
  {
    "path": "README.md",
    "content": "<div align = center>\n\n<img src=\"https://raw.githubusercontent.com/hyprwm/Hyprland/main/assets/header.svg\" width=\"750\" height=\"300\" alt=\"banner\">\n\n<br>\n\n[![Badge Workflow]][Workflow]\n[![Badge License]][License] \n![Badge Language] \n[![Badge Pull Requests]][Pull Requests] \n[![Badge Issues]][Issues] \n![Badge Hi Mom]<br>\n\n<br>\n\nHyprland is a 100% independent, dynamic tiling Wayland compositor that doesn't sacrifice on its looks.\n\nIt provides the latest Wayland features, is highly customizable, has all the eyecandy, the most powerful plugins,\neasy IPC, much more QoL stuff than other compositors and more...\n<br>\n<br>\n\n---\n\n**[<kbd> <br> Install <br> </kbd>][Install]** \n**[<kbd> <br> Quick Start <br> </kbd>][Quick Start]** \n**[<kbd> <br> Configure <br> </kbd>][Configure]** \n**[<kbd> <br> Contribute <br> </kbd>][Contribute]**\n\n---\n\n<br>\n\n</div>\n\n# Features\n\n- All of the eyecandy: gradient borders, blur, animations, shadows and much more\n- A lot of customization\n- 100% independent, no wlroots, no libweston, no kwin, no mutter.\n- Custom bezier curves for the best animations\n- Powerful plugin support\n- Built-in plugin manager\n- Tearing support for better gaming performance\n- Easily expandable and readable codebase\n- Fast and active development\n- Not afraid to provide bleeding-edge features\n- Config reloaded instantly upon saving\n- Fully dynamic workspaces\n- Two built-in layouts and more available as plugins\n- Global keybinds passed to your apps of choice\n- Tiling/pseudotiling/floating/fullscreen windows\n- Special workspaces (scratchpads)\n- Window groups (tabbed mode)\n- Powerful window/monitor/layer rules\n- Socket-based IPC\n- Native IME and Input Panels Support\n- and much more...\n\n<br>\n<br>\n\n<div align = center>\n\n# Gallery\n\n<br>\n\n![Preview A]\n\n<br>\n\n![Preview B]\n\n<br>\n\n![Preview C]\n\n<br>\n<br>\n\n</div>\n\n# Special Thanks\n\n<br>\n\n**[wlroots]** - *For powering Hyprland in the past*\n\n**[tinywl]** - *For showing how 2 do stuff*\n\n**[Sway]** - *For showing how 2 do stuff the overkill way*\n\n**[Vivarium]** - *For showing how 2 do stuff the simple way*\n\n**[dwl]** - *For showing how 2 do stuff the hacky way*\n\n**[Wayfire]** - *For showing how 2 do some graphics stuff*\n\n\n<!----------------------------------------------------------------------------->\n\n[Configure]: https://wiki.hypr.land/Configuring/\n[Stars]: https://starchart.cc/hyprwm/Hyprland\n[Hypr]: https://github.com/hyprwm/Hypr\n\n[Pull Requests]: https://github.com/hyprwm/Hyprland/pulls\n[Issues]: https://github.com/hyprwm/Hyprland/issues\n[Todo]: https://github.com/hyprwm/Hyprland/projects?type=beta\n\n[Contribute]: https://wiki.hypr.land/Contributing-and-Debugging/\n[Install]: https://wiki.hypr.land/Getting-Started/Installation/\n[Quick Start]: https://wiki.hypr.land/Getting-Started/Master-Tutorial/\n[Workflow]: https://github.com/hyprwm/Hyprland/actions/workflows/ci.yaml\n[License]: LICENSE\n\n\n<!----------------------------------{ Thanks }--------------------------------->\n\n[Vivarium]: https://github.com/inclement/vivarium\n[WlRoots]: https://gitlab.freedesktop.org/wlroots/wlroots\n[Wayfire]: https://github.com/WayfireWM/wayfire\n[TinyWl]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/tinywl/tinywl.c\n[Sway]: https://github.com/swaywm/sway\n[DWL]: https://codeberg.org/dwl/dwl\n\n<!----------------------------------{ Images }--------------------------------->\n\n[Preview A]: https://i.ibb.co/XxFY75Mk/greerggergerhtrytghjnyhjn.png\n[Preview B]: https://i.ibb.co/C1yTb0r/falf.png\n[Preview C]: https://i.ibb.co/2Yc4q835/hyprland-preview-b.png\n\n\n<!----------------------------------{ Badges }--------------------------------->\n\n[Badge Workflow]: https://github.com/hyprwm/Hyprland/actions/workflows/ci.yaml/badge.svg\n\n[Badge Issues]: https://img.shields.io/github/issues/hyprwm/Hyprland\n[Badge Pull Requests]: https://img.shields.io/github/issues-pr/hyprwm/Hyprland\n[Badge Language]: https://img.shields.io/github/languages/top/hyprwm/Hyprland\n[Badge License]: https://img.shields.io/github/license/hyprwm/Hyprland\n[Badge Lines]: https://img.shields.io/tokei/lines/github/hyprwm/Hyprland\n[Badge Hi Mom]: https://img.shields.io/badge/Hi-mom!-ff69b4\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Hyprland Development Security Policy\n\nIf you have a bug that affects the security of your system, you may\nwant to privately disclose it instead of making it immediately public.\n\n## Supported versions\n\n_Only_ the most recent release on Github is supported. There are no LTS releases.\n\n## What is not a security issue\n\nSome examples of issues that should not be reported as security issues:\n\n- An app can execute a command when ran outside of a sandbox\n- An app can write / read hyprland sockets when ran outside of a sandbox\n- Crashes\n- Things that are protected via permissions when the permission system is disabled\n\n## What is a security issue\n\nSome examples of issues that should be reported as security issues:\n\n- Sandboxed application executing arbitrary code via Hyprland\n- Application being able to modify Hyprland's code on the fly\n- Application being able to keylog / track user's activity beyond what the wayland protocols allow\n\n## How to report security issues\n\nPlease report your security issues via either of these channels:\n- Mail: `vaxry [at] vaxry [dot] net`\n- Matrix: `@vaxry:matrix.vaxry.net`\n- Discord: `@vaxry`\n"
  },
  {
    "path": "VERSION",
    "content": "0.54.0\n"
  },
  {
    "path": "assets/hyprland-portals.conf",
    "content": "[preferred]\ndefault=hyprland;gtk"
  },
  {
    "path": "docs/Hyprland.1",
    "content": ".\\\" Automatically generated by Pandoc 3.1.3\n.\\\"\n.\\\" Define V font for inline verbatim, using C font in formats\n.\\\" that render this, and otherwise B font.\n.ie \"\\f[CB]x\\f[]\"x\" \\{\\\n. ftr V B\n. ftr VI BI\n. ftr VB B\n. ftr VBI BI\n.\\}\n.el \\{\\\n. ftr V CR\n. ftr VI CI\n. ftr VB CB\n. ftr VBI CBI\n.\\}\n.TH \"Hyprland\" \"1\" \"\" \"\" \"Hyprland User Manual\"\n.hy\n.SH NAME\n.PP\nHyprland - Dynamic tiling Wayland compositor\n.SH SYNOPSIS\n.PP\n\\f[B]Hyprland\\f[R] [\\f[I]arg [...]\\f[R]].\n.SH DESCRIPTION\n.PP\n\\f[B]Hyprland\\f[R] is an independent, highly customizable, dynamic\ntiling Wayland compositor that doesn\\[aq]t sacrifice on its looks.\n.PP\nYou can launch Hyprland by either going into a TTY and executing\n\\f[B]Hyprland\\f[R], or with a login manager.\n.SH NOTICE\n.PP\nHyprland is still in pretty early development compared to some other\nWayland compositors.\n.PP\nAlthough Hyprland is pretty stable, it may have some bugs.\n.SH CONFIGURATION\n.PP\nFor configuration information please see\n<\\f[I]https://github.com/hyprwm/Hyprland/wiki\\f[R]>.\n.SH OPTIONS\n.TP\n\\f[B]-h\\f[R], \\f[B]--help\\f[R]\nShow command usage.\n.TP\n\\f[B]-c\\f[R], \\f[B]--config\\f[R]\nSpecify config file to use.\n.TP\n\\f[B]--socket\\f[R]\nSets the Wayland socket name (for Wayland socket handover)\n.TP\n\\f[B]--wayland-fd\\f[R]\nSets the Wayland socket file descriptor (for Wayland socket handover)\n.SH BUGS\n.TP\nSubmit bug reports and request features online at:\n<\\f[I]https://github.com/hyprwm/Hyprland/issues\\f[R]>\n.SH SEE ALSO\n.PP\nSources at: <\\f[I]https://github.com/hyprwm/Hyprland\\f[R]>\n.SH COPYRIGHT\n.PP\nCopyright (c) 2022, vaxerski\n.SH AUTHORS\nVaxerski <\\f[I]https://github.com/vaxerski\\f[R]>.\n"
  },
  {
    "path": "docs/Hyprland.1.rst",
    "content": ":title: Hyprland\n:author: Vaxerski <*https://github.com/vaxerski*>\n\nNAME\n====\n\nHyprland - Dynamic tiling Wayland compositor\n\nSYNOPSIS\n========\n\n**Hyprland** [*arg [...]*].\n\nDESCRIPTION\n===========\n\n**Hyprland** is an independent, highly customizable, \ndynamic tiling Wayland compositor that doesn't sacrifice on its looks. \n\nYou can launch Hyprland by either going into a TTY and\nexecuting **Hyprland**, or with a login manager.\n\nNOTICE\n======\n\nHyprland is still in pretty early development compared to some other Wayland compositors.\n\nAlthough Hyprland is pretty stable, it may have some bugs.\n\nCONFIGURATION\n=============\n\nFor configuration information please see <*https://github.com/hyprwm/Hyprland/wiki*>.\n\nOPTIONS\n=======\n\n**-h**, **--help**\n    Show command usage.\n\n**-c**, **--config**\n    Specify config file to use.\n\n**--socket**\n    Sets the Wayland socket name (for Wayland socket handover)\n\n**--wayland-fd**\n    Sets the Wayland socket file descriptor (for Wayland socket handover)\n\nBUGS\n====\n\nSubmit bug reports and request features online at:\n    <*https://github.com/hyprwm/Hyprland/issues*>\n\nSEE ALSO\n========\n\nSources at: <*https://github.com/hyprwm/Hyprland*>\n\nCOPYRIGHT\n=========\n\nCopyright (c) 2022, vaxerski\n"
  },
  {
    "path": "docs/ISSUE_GUIDELINES.md",
    "content": "# Issue Guidelines\n\nFirst of all, please remember to:\n- Check that your issue is not a duplicate\n- Read the [FAQ](https://wiki.hypr.land/FAQ/)\n- Read the [Configuring Page](https://wiki.hypr.land/Configuring/)\n\n<br/>\n\n# Reporting suggestions\nSuggestions are welcome.\n\nMany features can be implemented using bash scripts and Hyprland sockets, read up on those [Here](https://wiki.hypr.land/IPC). Please do not suggest features that can be implemented as such.\n\n<br/>\n\n# Reporting bugs\n\nAll bug reports should have the following:\n- Steps to reproduce\n- Expected outcome\n- Noted outcome\n\nIf your bug is one that doesn't crash Hyprland, but feels like invalid behavior, that's all you need to say.\n\nIf your bug crashes Hyprland, append additionally:\n- The Hyprland log\n- Your config\n- (v0.22.0beta and up) The Hyprland Crash Report\n- (v0.21.0beta and below) Coredump / Coredump analysis (with a stacktrace)\n\n**Important**: Please do NOT use any package for reporting bugs! Clone and compile from source.\n\n## Obtaining the Hyprland log\nIf you are in a TTY, and the hyprland session that crashed was the last one you launched, the log will be printed with\n```\ncat $XDG_RUNTIME_DIR/hypr/$(ls -t $XDG_RUNTIME_DIR/hypr | head -n 1)/hyprland.log\n```\nfeel free to send it to a file, save, copy, etc.\n\nif you are in a Hyprland session, and you want the log of the last session, use\n```\ncat $XDG_RUNTIME_DIR/hypr/$(ls -t $XDG_RUNTIME_DIR/hypr | head -n 2 | tail -n 1)/hyprland.log\n```\n\nbasically, directories in $XDG_RUNTIME_DIR/hypr are your sessions.\n\n## Obtaining the Hyprland Crash Report (v0.22.0beta and up)\n\nIf you have `$XDG_CACHE_HOME` set, the crash report directory is `$XDG_CACHE_HOME/hyprland`. If not, it's `$HOME/.cache/hyprland`.\n\nGo to the crash report directory and you should find a file named `hyprlandCrashReport[XXXX].txt` where `[XXXX]` is the PID of the process that crashed.\n\nAttach that file to your issue.\n## Obtaining the Hyprland coredump (v0.21.0beta and below)\nIf you are on systemd, you can simply use\n```\ncoredumpctl\n```\nthen go to the end (press END on your keyboard) and remember the PID of the last `Hyprland` occurrence. It's the first number after the time, for example `2891`.\n\nexit coredumpctl (ctrl+c) and use\n```\ncoredumpctl info [PID]\n```\nwhere `[PID]` is the PID you remembered.\n\n## Obtaining the debug Hyprland coredump\nA debug coredump provides more information for debugging and may speed up the process of fixing the bug.\n\nMake sure you're on latest git. Run `git pull --recurse-submodules` to sync everything.\n\n1. [Compile Hyprland with debug mode](http://wiki.hypr.land/Contributing-and-Debugging/#build-in-debug-mode)\n> Note: The config file used will be `hyprlandd.conf` instead of `hyprland.conf`\n\n2. `cd ~`\n3. For your own convenience, launch Hyprland from a tty with the envvar `ASAN_OPTIONS=\"log_path=asan.log\" ~/path/to/Hyprland`\n4. Reproduce the crash. Hyprland should instantly close.\n5. Check out your `~` and find a file called `asan.log.XXXXX` where `XXXXX` will be a number corresponding to the PID of the Hyprland instance that crashed.\n6. That is your coredump. Attach it to your issue.\n"
  },
  {
    "path": "docs/hyprctl.1",
    "content": ".\\\" Automatically generated by Pandoc 3.1.3\n.\\\"\n.\\\" Define V font for inline verbatim, using C font in formats\n.\\\" that render this, and otherwise B font.\n.ie \"\\f[CB]x\\f[]\"x\" \\{\\\n. ftr V B\n. ftr VI BI\n. ftr VB B\n. ftr VBI BI\n.\\}\n.el \\{\\\n. ftr V CR\n. ftr VI CI\n. ftr VB CB\n. ftr VBI CBI\n.\\}\n.TH \"hyprctl\" \"1\" \"\" \"\" \"hyprctl User Manual\"\n.hy\n.SH NAME\n.PP\nhyprctl - Utility for controlling parts of Hyprland from a CLI or a\nscript\n.SH SYNOPSIS\n.PP\n\\f[B]hyprctl\\f[R] [\\f[I](opt)flags\\f[R]] [\\f[B]command\\f[R]]\n[\\f[I](opt)args\\f[R]]\n.SH DESCRIPTION\n.PP\n\\f[B]hyprctl\\f[R] is a utility for controlling some parts of the\ncompositor from a CLI or a script.\n.SH CONTROL COMMANDS\n.PP\n\\f[B]dispatch\\f[R]\n.RS\n.PP\nCall a dispatcher with an argument.\n.PP\nAn argument must be present.\nFor dispatchers without parameters it can be anything.\n.PP\nReturns: \\f[I]ok\\f[R] on success, and an error message on failure.\n.TP\nExamples:\n\\f[B]hyprctl\\f[R] \\f[I]dispatch exec kitty\\f[R]\n.RS\n.PP\n\\f[B]hyprctl\\f[R] \\f[I]dispatch pseudo x\\f[R]\n.RE\n.RE\n.PP\n\\f[B]keyword\\f[R]\n.RS\n.PP\nSet a config keyword dynamically.\n.PP\nReturns: \\f[I]ok\\f[R] on success, and an error message on failure.\n.TP\nExamples:\n\\f[B]hyprctl\\f[R] \\f[I]keyword bind SUPER,0,pseudo\\f[R]\n.RS\n.PP\n\\f[B]hyprctl\\f[R] \\f[I]keyword general:border_size 10\\f[R]\n.RE\n.RE\n.PP\n\\f[B]reload\\f[R]\n.RS\n.PP\nForce a reload of the config file.\n.RE\n.PP\n\\f[B]kill\\f[R]\n.RS\n.PP\nEnter kill mode, where you can kill an app by clicking on it.\nYou can exit by pressing ESCAPE.\n.RE\n.SH INFO COMMANDS\n.PP\n\\f[B]version\\f[R]\n.RS\n.PP\nPrints the Hyprland version, flags, commit and branch of build.\n.RE\n.PP\n\\f[B]monitors\\f[R]\n.RS\n.PP\nLists all the outputs with their properties.\n.RE\n.PP\n\\f[B]workspaces\\f[R]\n.RS\n.PP\nLists all workspaces with their properties.\n.RE\n.PP\n\\f[B]clients\\f[R]\n.RS\n.PP\nLists all windows with their properties.\n.RE\n.PP\n\\f[B]devices\\f[R]\n.RS\n.PP\nLists all connected input devices.\n.RE\n.PP\n\\f[B]activewindow\\f[R]\n.RS\n.PP\nReturns the active window name.\n.RE\n.PP\n\\f[B]layers\\f[R]\n.RS\n.PP\nLists all the layers.\n.RE\n.PP\n\\f[B]splash\\f[R]\n.RS\n.PP\nReturns the current random splash.\n.RE\n.SH OPTIONS\n.PP\n\\f[B]--batch\\f[R]\n.RS\n.PP\nSpecify a batch of commands to execute.\n.TP\nExample:\n\\f[B]hyprctl\\f[R] \\f[I]--batch \\[dq]keyword general:border_size 2 ;\nkeyword general:gaps_out 20\\[dq]\\f[R]\n.RS\n.PP\n\\f[I];\\f[R] separates the commands.\n.RE\n.RE\n.PP\n\\f[B]-j\\f[R]\n.RS\n.PP\nOutputs information in JSON.\n.RE\n.SH BUGS\n.TP\nSubmit bug reports and request features online at:\n<\\f[I]https://github.com/hyprwm/Hyprland/issues\\f[R]>\n.SH SEE ALSO\n.PP\nSources at: <\\f[I]https://github.com/hyprwm/Hyprland\\f[R]>\n.SH COPYRIGHT\n.PP\nCopyright (c) 2022, vaxerski\n.SH AUTHORS\nVaxerski <\\f[I]https://github.com/vaxerski\\f[R]>.\n"
  },
  {
    "path": "docs/hyprctl.1.rst",
    "content": ":title: hyprctl(1)\n:author: Vaxerski <*https://github.com/vaxerski*>\n\nNAME\n====\n\nhyprctl - Utility for controlling parts of Hyprland from a CLI or a script\n\nSYNOPSIS\n========\n\n**hyprctl** [*(opt)flags*] [**command**] [*(opt)args*]\n\nDESCRIPTION\n===========\n\n**hyprctl** is a utility for controlling some parts of the compositor from a CLI or a script.\n\nCONTROL COMMANDS\n================\n\n**dispatch**\n\n    Call a dispatcher with an argument.\n\n    An argument must be present.\n    For dispatchers without parameters it can be anything.\n\n    Returns: *ok* on success, and an error message on failure.\n\n    Examples:\n        **hyprctl** *dispatch exec kitty*\n\n        **hyprctl** *dispatch pseudo x*\n\n**keyword**\n\n    Set a config keyword dynamically.\n\n    Returns: *ok* on success, and an error message on failure.\n\n    Examples:\n        **hyprctl** *keyword bind SUPER,0,pseudo*\n\n        **hyprctl** *keyword general:border_size 10*\n\n**reload**\n\n    Force a reload of the config file.\n\n**kill**\n\n    Enter kill mode, where you can kill an app by clicking on it.\n    You can exit by pressing ESCAPE.\n\nINFO COMMANDS\n=============\n\n**version**\n\n    Prints the Hyprland version, flags, commit and branch of build.\n\n**monitors**\n\n    Lists all the outputs with their properties.\n\n**workspaces**\n\n    Lists all workspaces with their properties.\n\n**clients**\n\n    Lists all windows with their properties.\n\n**devices**\n\n    Lists all connected input devices.\n\n**activewindow**\n\n    Returns the active window name.\n\n**layers**\n\n    Lists all the layers.\n\n**splash**\n\n    Returns the current random splash.\n\nOPTIONS\n=======\n\n**--batch**\n\n    Specify a batch of commands to execute.\n\n    Example:\n        **hyprctl** *--batch \"keyword general:border_size 2 ; keyword general:gaps_out 20\"*\n\n        *;* separates the commands.\n\n**-j**\n\n    Outputs information in JSON.\n\nBUGS\n====\n\nSubmit bug reports and request features online at:\n    <*https://github.com/hyprwm/Hyprland/issues*>\n\nSEE ALSO\n========\n\nSources at: <*https://github.com/hyprwm/Hyprland*>\n\nCOPYRIGHT\n=========\n\nCopyright (c) 2022, vaxerski\n"
  },
  {
    "path": "example/hyprland.conf",
    "content": "# This is an example Hyprland config file.\n# Refer to the wiki for more information.\n# https://wiki.hypr.land/Configuring/\n\n# Please note not all available settings / options are set here.\n# For a full list, see the wiki\n\n# You can split this configuration into multiple files\n# Create your files separately and then link them to this file like this:\n# source = ~/.config/hypr/myColors.conf\n\n\n################\n### MONITORS ###\n################\n\n# See https://wiki.hypr.land/Configuring/Monitors/\nmonitor=,preferred,auto,auto\n\n\n###################\n### MY PROGRAMS ###\n###################\n\n# See https://wiki.hypr.land/Configuring/Keywords/\n\n# Set programs that you use\n$terminal = kitty\n$fileManager = dolphin\n$menu = hyprlauncher\n\n\n#################\n### AUTOSTART ###\n#################\n\n# Autostart necessary processes (like notifications daemons, status bars, etc.)\n# Or execute your favorite apps at launch like this:\n\n# exec-once = $terminal\n# exec-once = nm-applet &\n# exec-once = waybar & hyprpaper & firefox\n\n\n#############################\n### ENVIRONMENT VARIABLES ###\n#############################\n\n# See https://wiki.hypr.land/Configuring/Environment-variables/\n\nenv = XCURSOR_SIZE,24\nenv = HYPRCURSOR_SIZE,24\n\n\n###################\n### PERMISSIONS ###\n###################\n\n# See https://wiki.hypr.land/Configuring/Permissions/\n# Please note permission changes here require a Hyprland restart and are not applied on-the-fly\n# for security reasons\n\n# ecosystem {\n#   enforce_permissions = 1\n# }\n\n# permission = /usr/(bin|local/bin)/grim, screencopy, allow\n# permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow\n# permission = /usr/(bin|local/bin)/hyprpm, plugin, allow\n\n\n#####################\n### LOOK AND FEEL ###\n#####################\n\n# Refer to https://wiki.hypr.land/Configuring/Variables/\n\n# https://wiki.hypr.land/Configuring/Variables/#general\ngeneral {\n    gaps_in = 5\n    gaps_out = 20\n\n    border_size = 2\n\n    # https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors\n    col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg\n    col.inactive_border = rgba(595959aa)\n\n    # Set to true enable resizing windows by clicking and dragging on borders and gaps\n    resize_on_border = false\n\n    # Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on\n    allow_tearing = false\n\n    layout = dwindle\n}\n\n# https://wiki.hypr.land/Configuring/Variables/#decoration\ndecoration {\n    rounding = 10\n    rounding_power = 2\n\n    # Change transparency of focused and unfocused windows\n    active_opacity = 1.0\n    inactive_opacity = 1.0\n\n    shadow {\n        enabled = true\n        range = 4\n        render_power = 3\n        color = rgba(1a1a1aee)\n    }\n\n    # https://wiki.hypr.land/Configuring/Variables/#blur\n    blur {\n        enabled = true\n        size = 3\n        passes = 1\n\n        vibrancy = 0.1696\n    }\n}\n\n# https://wiki.hypr.land/Configuring/Variables/#animations\nanimations {\n    enabled = yes, please :)\n\n    # Default curves, see https://wiki.hypr.land/Configuring/Animations/#curves\n    #        NAME,           X0,   Y0,   X1,   Y1\n    bezier = easeOutQuint,   0.23, 1,    0.32, 1\n    bezier = easeInOutCubic, 0.65, 0.05, 0.36, 1\n    bezier = linear,         0,    0,    1,    1\n    bezier = almostLinear,   0.5,  0.5,  0.75, 1\n    bezier = quick,          0.15, 0,    0.1,  1\n\n    # Default animations, see https://wiki.hypr.land/Configuring/Animations/\n    #           NAME,          ONOFF, SPEED, CURVE,        [STYLE]\n    animation = global,        1,     10,    default\n    animation = border,        1,     5.39,  easeOutQuint\n    animation = windows,       1,     4.79,  easeOutQuint\n    animation = windowsIn,     1,     4.1,   easeOutQuint, popin 87%\n    animation = windowsOut,    1,     1.49,  linear,       popin 87%\n    animation = fadeIn,        1,     1.73,  almostLinear\n    animation = fadeOut,       1,     1.46,  almostLinear\n    animation = fade,          1,     3.03,  quick\n    animation = layers,        1,     3.81,  easeOutQuint\n    animation = layersIn,      1,     4,     easeOutQuint, fade\n    animation = layersOut,     1,     1.5,   linear,       fade\n    animation = fadeLayersIn,  1,     1.79,  almostLinear\n    animation = fadeLayersOut, 1,     1.39,  almostLinear\n    animation = workspaces,    1,     1.94,  almostLinear, fade\n    animation = workspacesIn,  1,     1.21,  almostLinear, fade\n    animation = workspacesOut, 1,     1.94,  almostLinear, fade\n    animation = zoomFactor,    1,     7,     quick\n}\n\n# Ref https://wiki.hypr.land/Configuring/Workspace-Rules/\n# \"Smart gaps\" / \"No gaps when only\"\n# uncomment all if you wish to use that.\n# workspace = w[tv1], gapsout:0, gapsin:0\n# workspace = f[1], gapsout:0, gapsin:0\n# windowrule {\n#     name = no-gaps-wtv1\n#     match:float = false\n#     match:workspace = w[tv1]\n#\n#     border_size = 0\n#     rounding = 0\n# }\n#\n# windowrule {\n#     name = no-gaps-f1\n#     match:float = false\n#     match:workspace = f[1]\n#\n#     border_size = 0\n#     rounding = 0\n# }\n\n# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more\ndwindle {\n    pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below\n    preserve_split = true # You probably want this\n}\n\n# See https://wiki.hypr.land/Configuring/Master-Layout/ for more\nmaster {\n    new_status = master\n}\n\n# https://wiki.hypr.land/Configuring/Variables/#misc\nmisc {\n    force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers\n    disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(\n}\n\n\n#############\n### INPUT ###\n#############\n\n# https://wiki.hypr.land/Configuring/Variables/#input\ninput {\n    kb_layout = us\n    kb_variant =\n    kb_model =\n    kb_options =\n    kb_rules =\n\n    follow_mouse = 1\n\n    sensitivity = 0 # -1.0 - 1.0, 0 means no modification.\n\n    touchpad {\n        natural_scroll = false\n    }\n}\n\n# See https://wiki.hypr.land/Configuring/Gestures\ngesture = 3, horizontal, workspace\n\n# Example per-device config\n# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more\ndevice {\n    name = epic-mouse-v1\n    sensitivity = -0.5\n}\n\n\n###################\n### KEYBINDINGS ###\n###################\n\n# See https://wiki.hypr.land/Configuring/Keywords/\n$mainMod = SUPER # Sets \"Windows\" key as main modifier\n\n# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more\nbind = $mainMod, Q, exec, $terminal\nbind = $mainMod, C, killactive,\nbind = $mainMod, M, exec, command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch exit\nbind = $mainMod, E, exec, $fileManager\nbind = $mainMod, V, togglefloating,\nbind = $mainMod, R, exec, $menu\nbind = $mainMod, P, pseudo, # dwindle\nbind = $mainMod, J, layoutmsg, togglesplit # dwindle\n\n# Move focus with mainMod + arrow keys\nbind = $mainMod, left, movefocus, l\nbind = $mainMod, right, movefocus, r\nbind = $mainMod, up, movefocus, u\nbind = $mainMod, down, movefocus, d\n\n# Switch workspaces with mainMod + [0-9]\nbind = $mainMod, 1, workspace, 1\nbind = $mainMod, 2, workspace, 2\nbind = $mainMod, 3, workspace, 3\nbind = $mainMod, 4, workspace, 4\nbind = $mainMod, 5, workspace, 5\nbind = $mainMod, 6, workspace, 6\nbind = $mainMod, 7, workspace, 7\nbind = $mainMod, 8, workspace, 8\nbind = $mainMod, 9, workspace, 9\nbind = $mainMod, 0, workspace, 10\n\n# Move active window to a workspace with mainMod + SHIFT + [0-9]\nbind = $mainMod SHIFT, 1, movetoworkspace, 1\nbind = $mainMod SHIFT, 2, movetoworkspace, 2\nbind = $mainMod SHIFT, 3, movetoworkspace, 3\nbind = $mainMod SHIFT, 4, movetoworkspace, 4\nbind = $mainMod SHIFT, 5, movetoworkspace, 5\nbind = $mainMod SHIFT, 6, movetoworkspace, 6\nbind = $mainMod SHIFT, 7, movetoworkspace, 7\nbind = $mainMod SHIFT, 8, movetoworkspace, 8\nbind = $mainMod SHIFT, 9, movetoworkspace, 9\nbind = $mainMod SHIFT, 0, movetoworkspace, 10\n\n# Example special workspace (scratchpad)\nbind = $mainMod, S, togglespecialworkspace, magic\nbind = $mainMod SHIFT, S, movetoworkspace, special:magic\n\n# Scroll through existing workspaces with mainMod + scroll\nbind = $mainMod, mouse_down, workspace, e+1\nbind = $mainMod, mouse_up, workspace, e-1\n\n# Move/resize windows with mainMod + LMB/RMB and dragging\nbindm = $mainMod, mouse:272, movewindow\nbindm = $mainMod, mouse:273, resizewindow\n\n# Laptop multimedia keys for volume and LCD brightness\nbindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+\nbindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-\nbindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle\nbindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle\nbindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+\nbindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%-\n\n# Requires playerctl\nbindl = , XF86AudioNext, exec, playerctl next\nbindl = , XF86AudioPause, exec, playerctl play-pause\nbindl = , XF86AudioPlay, exec, playerctl play-pause\nbindl = , XF86AudioPrev, exec, playerctl previous\n\n##############################\n### WINDOWS AND WORKSPACES ###\n##############################\n\n# See https://wiki.hypr.land/Configuring/Window-Rules/ for more\n# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules\n\n# Example windowrules that are useful\n\nwindowrule {\n    # Ignore maximize requests from all apps. You'll probably like this.\n    name = suppress-maximize-events\n    match:class = .*\n\n    suppress_event = maximize\n}\n\nwindowrule {\n    # Fix some dragging issues with XWayland\n    name = fix-xwayland-drags\n    match:class = ^$\n    match:title = ^$\n    match:xwayland = true\n    match:float = true\n    match:fullscreen = false\n    match:pin = false\n\n    no_focus = true\n}\n\n# Hyprland-run windowrule\nwindowrule {\n    name = move-hyprland-run\n\n    match:class = hyprland-run\n\n    move = 20 monitor_h-120\n    float = yes\n}\n"
  },
  {
    "path": "example/hyprland.desktop.in",
    "content": "[Desktop Entry]\nName=Hyprland\nComment=An intelligent dynamic tiling Wayland compositor\nExec=@PREFIX@/@CMAKE_INSTALL_BINDIR@/start-hyprland\nType=Application\nDesktopNames=Hyprland\nKeywords=tiling;wayland;compositor;\n"
  },
  {
    "path": "example/hyprland.service",
    "content": "; a primitive systemd --user example\n[Unit]\nDescription = %p\nBindsTo     = graphical-session.target\nUpholds     = swaybg@333333.service\n\n[Service]\nType            = notify\nExecStart       = /usr/bin/Hyprland\n\n[Install]\nWantedBy = default.target\n"
  },
  {
    "path": "example/launch.json",
    "content": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"(gdb) Launch\",\n            \"type\": \"cppdbg\",\n            \"request\": \"launch\",\n            \"program\": \"${workspaceFolder}/build/Hyprland\",\n            \"args\": [],\n            \"stopAtEntry\": false,\n            \"cwd\": \"${fileDirname}\",\n            \"externalConsole\": false,\n            \"MIMode\": \"gdb\",\n            \"setupCommands\": [\n                {\n                    \"description\": \"Enable pretty-printing for gdb\",\n                    \"text\": \"-enable-pretty-printing\",\n                    \"ignoreFailures\": true\n                }\n            ]\n        },\n        \n    ]\n}"
  },
  {
    "path": "example/screenShader.frag",
    "content": "//\n// Example blue light filter shader.\n//\n\n#version 300 es\n\nprecision mediump float;\nin vec2 v_texcoord;\nlayout(location = 0) out vec4 fragColor;\nuniform sampler2D tex;\n\nvoid main() {\n\n    vec4 pixColor = texture(tex, v_texcoord);\n\n    pixColor[2] *= 0.8;\n\n    fragColor = pixColor;\n}\n"
  },
  {
    "path": "example/swaybg@.service",
    "content": "; a primitive systemd --user example\n; see example/hyprland.service for more details\n[Unit]\nDescription = %p\nBindsTo     = hyprland.service\nWants       = hyprland.service\nAfter       = hyprland.service\n\n[Service]\nExecStart = /usr/bin/swaybg --color #%i\n\n[Install]\nWantedBy = default.target\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"Hyprland is a dynamic tiling Wayland compositor that doesn't sacrifice on its looks\";\n\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n\n    # <https://github.com/nix-systems/nix-systems>\n    systems.url = \"github:nix-systems/default-linux\";\n\n    aquamarine = {\n      url = \"github:hyprwm/aquamarine\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.hyprutils.follows = \"hyprutils\";\n      inputs.hyprwayland-scanner.follows = \"hyprwayland-scanner\";\n    };\n\n    hyprcursor = {\n      url = \"github:hyprwm/hyprcursor\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.hyprlang.follows = \"hyprlang\";\n    };\n\n    hyprgraphics = {\n      url = \"github:hyprwm/hyprgraphics\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.hyprutils.follows = \"hyprutils\";\n    };\n\n    hyprland-protocols = {\n      url = \"github:hyprwm/hyprland-protocols\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n    };\n\n    hyprland-guiutils = {\n      url = \"github:hyprwm/hyprland-guiutils\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.aquamarine.follows = \"aquamarine\";\n      inputs.hyprgraphics.follows = \"hyprgraphics\";\n      inputs.hyprutils.follows = \"hyprutils\";\n      inputs.hyprlang.follows = \"hyprlang\";\n      inputs.hyprwayland-scanner.follows = \"hyprwayland-scanner\";\n    };\n\n    hyprlang = {\n      url = \"github:hyprwm/hyprlang\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.hyprutils.follows = \"hyprutils\";\n    };\n\n    hyprutils = {\n      url = \"github:hyprwm/hyprutils\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n    };\n\n    hyprwayland-scanner = {\n      url = \"github:hyprwm/hyprwayland-scanner\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n    };\n\n    hyprwire = {\n      url = \"github:hyprwm/hyprwire\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.hyprutils.follows = \"hyprutils\";\n    };\n\n    xdph = {\n      url = \"github:hyprwm/xdg-desktop-portal-hyprland\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.systems.follows = \"systems\";\n      inputs.hyprland-protocols.follows = \"hyprland-protocols\";\n      inputs.hyprlang.follows = \"hyprlang\";\n      inputs.hyprutils.follows = \"hyprutils\";\n      inputs.hyprwayland-scanner.follows = \"hyprwayland-scanner\";\n    };\n\n    pre-commit-hooks = {\n      url = \"github:cachix/git-hooks.nix\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n  };\n\n  outputs =\n    inputs@{\n      self,\n      nixpkgs,\n      systems,\n      ...\n    }:\n    let\n      inherit (nixpkgs) lib;\n      eachSystem = lib.genAttrs (import systems);\n      pkgsFor = eachSystem (\n        system:\n        import nixpkgs {\n          localSystem = system;\n          overlays = with self.overlays; [\n            hyprland-packages\n            hyprland-extras\n          ];\n        }\n      );\n      pkgsCrossFor = eachSystem (\n        system: crossSystem:\n        import nixpkgs {\n          localSystem = system;\n          inherit crossSystem;\n          overlays = with self.overlays; [\n            hyprland-packages\n            hyprland-extras\n          ];\n        }\n      );\n      pkgsDebugFor = eachSystem (\n        system:\n        import nixpkgs {\n          localSystem = system;\n          overlays = with self.overlays; [\n            hyprland-debug\n          ];\n        }\n      );\n      pkgsDebugCrossFor = eachSystem (\n        system: crossSystem:\n        import nixpkgs {\n          localSystem = system;\n          inherit crossSystem;\n          overlays = with self.overlays; [\n            hyprland-debug\n          ];\n        }\n      );\n    in\n    {\n      overlays = import ./nix/overlays.nix { inherit self lib inputs; };\n\n      checks = eachSystem (\n        system:\n        (lib.filterAttrs (\n          n: _: (lib.hasPrefix \"hyprland\" n) && !(lib.hasSuffix \"debug\" n)\n        ) self.packages.${system})\n        // {\n          inherit (self.packages.${system}) xdg-desktop-portal-hyprland;\n          pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {\n            src = ./.;\n            hooks = {\n              hyprland-treewide-formatter = {\n                enable = true;\n                entry = \"${self.formatter.${system}}/bin/hyprland-treewide-formatter\";\n                pass_filenames = false;\n                excludes = [ \"subprojects\" ];\n                always_run = true;\n              };\n            };\n          };\n        }\n        // (import ./nix/tests inputs pkgsFor.${system})\n      );\n\n      packages = eachSystem (system: {\n        default = self.packages.${system}.hyprland;\n        inherit (pkgsFor.${system})\n          # hyprland-packages\n          hyprland\n          hyprland-unwrapped\n          hyprland-with-tests\n          # hyprland-extras\n          xdg-desktop-portal-hyprland\n          ;\n        inherit (pkgsDebugFor.${system}) hyprland-debug;\n        hyprland-cross = (pkgsCrossFor.${system} \"aarch64-linux\").hyprland;\n        hyprland-debug-cross = (pkgsDebugCrossFor.${system} \"aarch64-linux\").hyprland-debug;\n      });\n\n      devShells = eachSystem (system: {\n        default =\n          pkgsFor.${system}.mkShell.override\n            {\n              inherit (self.packages.${system}.default) stdenv;\n            }\n            {\n              name = \"hyprland-shell\";\n              hardeningDisable = [ \"fortify\" ];\n              inputsFrom = [ pkgsFor.${system}.hyprland ];\n              packages = [ pkgsFor.${system}.clang-tools ];\n              inherit (self.checks.${system}.pre-commit-check) shellHook;\n            };\n      });\n\n      formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix { });\n\n      nixosModules.default = import ./nix/module.nix inputs;\n      homeManagerModules.default = import ./nix/hm-module.nix self;\n\n      # Hydra build jobs\n      # Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix\n      # or similar. Remember to filter large or incompatible attributes here. More eval jobs can\n      # be added by merging, e.g., self.packages // self.devShells.\n      hydraJobs = self.packages;\n    };\n}\n"
  },
  {
    "path": "hyprctl/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.19)\n\nproject(\n    hyprctl\n    DESCRIPTION \"Control utility for Hyprland\"\n)\n\npkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 hyprwire re2)\n\nfile(GLOB_RECURSE HYPRCTL_SRCFILES CONFIGURE_DEPENDS \"src/*.cpp\" \"hw-protocols/*.cpp\" \"include/*.hpp\")\n\nadd_executable(hyprctl ${HYPRCTL_SRCFILES})\n\ntarget_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps)\ntarget_include_directories(hyprctl PRIVATE \"hw-protocols\")\n\n# Hyprwire\n\nfunction(hyprprotocol protoPath protoName)\n  set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})\n  add_custom_command(\n    OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp\n           ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp\n           ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-spec.hpp\n    COMMAND hyprwire-scanner --client ${path}/${protoName}.xml\n            ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})\n  target_sources(hyprctl PRIVATE hw-protocols/${protoName}-client.cpp\n                                   hw-protocols/${protoName}-client.hpp\n                                   hw-protocols/${protoName}-spec.hpp)\nendfunction()\n\nhyprprotocol(hw-protocols hyprpaper_core)\n\n# binary\ninstall(TARGETS hyprctl)\n\n# shell completions\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprctl.bash\n        DESTINATION ${CMAKE_INSTALL_DATADIR}/bash-completion/completions\n        RENAME hyprctl)\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprctl.fish\n        DESTINATION ${CMAKE_INSTALL_DATADIR}/fish/vendor_completions.d)\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprctl.zsh\n        DESTINATION ${CMAKE_INSTALL_DATADIR}/zsh/site-functions\n        RENAME _hyprctl)\n"
  },
  {
    "path": "hyprctl/hw-protocols/hyprpaper_core.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"hyprpaper_core\" version=\"2\">\n  <copyright>\n    BSD 3-Clause License\n\n    Copyright (c) 2025, Hypr Development\n\n    Redistribution and use in source and binary forms, with or without\n    modification, are permitted provided that the following conditions are met:\n\n    1. Redistributions of source code must retain the above copyright notice, this\n        list of conditions and the following disclaimer.\n\n    2. Redistributions in binary form must reproduce the above copyright notice,\n        this list of conditions and the following disclaimer in the documentation\n        and/or other materials provided with the distribution.\n\n    3. Neither the name of the copyright holder nor the names of its\n        contributors may be used to endorse or promote products derived from\n        this software without specific prior written permission.\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n  </copyright>\n\n  <object name=\"hyprpaper_core_manager\" version=\"2\">\n    <description summary=\"manager object\">\n      This is the core manager object for hyprpaper operations\n    </description>\n\n    <c2s name=\"get_wallpaper_object\">\n      <description summary=\"Get a wallpaper object\">\n        Creates a wallpaper object\n      </description>\n      <returns iface=\"hyprpaper_wallpaper\"/>\n    </c2s>\n\n    <s2c name=\"add_monitor\">\n      <description summary=\"New monitor added\">\n        Emitted when a new monitor is added.\n      </description>\n      <arg name=\"monitor_name\" type=\"varchar\" summary=\"the monitor's name\"/>\n    </s2c>\n\n    <s2c name=\"remove_monitor\">\n      <description summary=\"A monitor was removed\">\n        Emitted when a monitor is removed.\n      </description>\n      <arg name=\"monitor_name\" type=\"varchar\" summary=\"the monitor's name\"/>\n    </s2c>\n\n    <c2s name=\"destroy\" destructor=\"true\">\n      <description summary=\"Destroy this object\">\n        Destroys this object. Children remain alive until destroyed.\n      </description>\n    </c2s>\n\n    <c2s name=\"get_status_object\" since=\"2\">\n      <description summary=\"Get a status object\">\n        Creates a status object\n      </description>\n      <returns iface=\"hyprpaper_status\"/>\n    </c2s>\n  </object>\n\n  <enum name=\"wallpaper_fit_mode\">\n    <value idx=\"0\" name=\"stretch\"/>\n    <value idx=\"1\" name=\"cover\"/>\n    <value idx=\"2\" name=\"contain\"/>\n    <value idx=\"3\" name=\"tile\"/>\n  </enum>\n\n  <enum name=\"wallpaper_errors\">\n    <value idx=\"0\" name=\"inert_wallpaper_object\" description=\"attempted to use an inert wallpaper object\"/>\n  </enum>\n\n  <enum name=\"applying_error\">\n    <value idx=\"0\" name=\"invalid_path\" description=\"path provided was invalid\"/>\n    <value idx=\"1\" name=\"invalid_monitor\" description=\"monitor provided was invalid\"/>\n    <value idx=\"2\" name=\"unknown_error\" description=\"unknown error\"/>\n  </enum>\n\n  <object name=\"hyprpaper_wallpaper\" version=\"1\">\n    <description summary=\"wallpaper object\">\n      This is an object describing a wallpaper\n    </description>\n\n    <c2s name=\"path\">\n      <description summary=\"Set a path\">\n        Set a file path for the wallpaper. This has to be an absolute path from the fs root.\n        This is required.\n      </description>\n      <arg name=\"wallpaper\" type=\"varchar\" summary=\"path\"/>\n    </c2s>\n\n    <c2s name=\"fit_mode\">\n      <description summary=\"Set a fit mode\">\n        Set a fit mode for the wallpaper. This is set to cover by default.\n      </description>\n      <arg name=\"fit_mode\" type=\"enum\" interface=\"wallpaper_fit_mode\" summary=\"path\"/>\n    </c2s>\n\n    <c2s name=\"monitor_name\">\n      <description summary=\"Set the monitor name\">\n        Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will\n        treat this as a wildcard fallback.\n\n        See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor\n        for tracking monitor names.\n      </description>\n      <arg name=\"monitor_name\" type=\"varchar\" summary=\"monitor name\"/>\n    </c2s>\n\n    <c2s name=\"apply\">\n      <description summary=\"Apply this wallpaper\">\n        Applies this object's state to the wallpaper state. Will emit .success on success,\n        and .failed on failure.\n\n        This object becomes inert after .succeess or .failed, the only valid operation\n        is to destroy it afterwards.\n      </description>\n    </c2s>\n\n    <s2c name=\"success\">\n      <description summary=\"Operation succeeded\">\n        Wallpaper was applied successfully.\n      </description>\n    </s2c>\n\n    <s2c name=\"failed\">\n      <description summary=\"Operation failed\">\n        Wallpaper was not applied. See the error field for more information.\n      </description>\n      <arg name=\"error\" type=\"enum\" interface=\"hyprpaper_wallpaper_application_error\" summary=\"path\"/>\n    </s2c>\n\n    <c2s name=\"destroy\" destructor=\"true\">\n      <description summary=\"Destroy this object\">\n        Destroys this object.\n      </description>\n    </c2s>\n  </object>\n\n  <object name=\"hyprpaper_status\" version=\"2\">\n    <description summary=\"status object\">\n      This is an object which will emit various status updates.\n    </description>\n    \n    <s2c name=\"active_wallpaper\">\n      <description summary=\"Active wallpaper state\">\n        Sends the active wallpaper for a given monitor. This will be emitted\n        immediately after binding, and then every time the path changes.\n      </description>\n      <arg name=\"monitor\" type=\"varchar\" summary=\"monitor name\"/>\n      <arg name=\"path\" type=\"varchar\" summary=\"wallpaper path\"/>\n    </s2c>\n\n    <c2s name=\"destroy\" destructor=\"true\">\n      <description summary=\"Destroy this object\">\n        Destroys this object.\n      </description>\n    </c2s>\n  </object>\n</protocol>\n"
  },
  {
    "path": "hyprctl/hyprctl.bash",
    "content": "_hyprctl_cmd_1 () {\n    hyprctl monitors | awk '/Monitor/{ print $2 }'\n}\n\n_hyprctl_cmd_3 () {\n    hyprctl clients | awk '/class/{print $2}'\n}\n\n_hyprctl_cmd_2 () {\n    hyprctl devices | sed -n '/Keyboard at/{n; s/^\\s\\+//; p}'\n}\n\n_hyprctl_cmd_0 () {\n    hyprpm list | awk '/Plugin/{print $4}'\n}\n\n_hyprctl () {\n    if [[ $(type -t _get_comp_words_by_ref) != function ]]; then\n        echo _get_comp_words_by_ref: function not defined.  Make sure the bash-completions system package is installed\n        return 1\n    fi\n\n    local words cword\n    _get_comp_words_by_ref -n \"$COMP_WORDBREAKS\" words cword\n\n    declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize sendkeystate)\n    declare -A literal_transitions\n    literal_transitions[0]=\"([120]=14 [43]=2 [125]=21 [81]=2 [3]=21 [51]=2 [50]=2 [128]=2 [89]=2 [58]=21 [8]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [102]=21 [133]=7 [100]=2 [137]=2 [22]=2 [19]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [78]=21 [114]=2 [37]=2 [151]=2 [116]=2 [121]=13 [123]=21 [39]=11 [42]=21 [79]=15 [118]=12)\"\n    literal_transitions[1]=\"([81]=2 [51]=2 [50]=2 [128]=2 [8]=2 [89]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [133]=7 [100]=2 [22]=2 [19]=2 [137]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [114]=2 [37]=2 [151]=2 [116]=2 [39]=11 [118]=12 [121]=13 [120]=14 [79]=15 [43]=2)\"\n    literal_transitions[3]=\"([139]=2 [63]=16 [64]=16 [45]=16 [105]=16 [27]=2 [26]=2 [52]=4 [5]=16 [66]=2 [67]=16 [129]=16 [113]=16 [12]=2 [74]=4 [99]=2 [35]=16 [152]=16 [98]=16 [59]=16 [117]=16 [41]=16 [17]=2 [138]=16 [154]=2 [122]=16)\"\n    literal_transitions[6]=\"([126]=2)\"\n    literal_transitions[10]=\"([56]=2)\"\n    literal_transitions[11]=\"([9]=2)\"\n    literal_transitions[12]=\"([14]=19 [80]=22)\"\n    literal_transitions[13]=\"([142]=2)\"\n    literal_transitions[14]=\"([0]=2 [84]=2 [2]=2 [85]=2 [4]=2 [87]=2 [88]=2 [90]=2 [91]=2 [92]=2 [93]=2 [94]=2 [96]=2 [15]=2 [18]=2 [103]=2 [21]=2 [104]=2 [23]=2 [24]=2 [28]=2 [29]=2 [30]=2 [108]=2 [111]=2 [32]=2 [112]=2 [36]=2 [38]=2 [119]=2 [124]=2 [46]=2 [47]=2 [48]=2 [49]=2 [53]=2 [55]=2 [131]=2 [132]=2 [134]=2 [135]=2 [60]=2 [136]=20 [141]=2 [65]=2 [144]=2 [145]=2 [68]=2 [147]=2 [70]=2 [71]=2 [72]=2 [73]=2 [148]=2 [75]=2 [76]=2 [150]=2 [153]=2)\"\n    literal_transitions[15]=\"([86]=4 [6]=4 [109]=4 [61]=4 [77]=4 [54]=4 [62]=4)\"\n    literal_transitions[16]=\"([40]=2 [44]=2)\"\n    literal_transitions[17]=\"([7]=23)\"\n    literal_transitions[18]=\"([31]=2 [149]=2)\"\n    literal_transitions[19]=\"([95]=2 [16]=2 [115]=2 [20]=2)\"\n    literal_transitions[20]=\"([106]=2 [82]=2 [127]=2 [1]=2 [83]=2)\"\n    literal_transitions[23]=\"([57]=21 [110]=21)\"\n    declare -A match_anything_transitions=([6]=17 [7]=2 [0]=1 [22]=2 [5]=18 [4]=2 [2]=17 [18]=2 [11]=17 [8]=2 [9]=2 [13]=17 [10]=17 [1]=1)\n    declare -A subword_transitions\n\n    local state=0\n    local word_index=1\n    while [[ $word_index -lt $cword ]]; do\n        local word=${words[$word_index]}\n\n        if [[ -v \"literal_transitions[$state]\" ]]; then\n            declare -A state_transitions\n            eval \"state_transitions=${literal_transitions[$state]}\"\n\n            local word_matched=0\n            for literal_id in $(seq 0 $((${#literals[@]} - 1))); do\n                if [[ ${literals[$literal_id]} = \"$word\" ]]; then\n                    if [[ -v \"state_transitions[$literal_id]\" ]]; then\n                        state=${state_transitions[$literal_id]}\n                        word_index=$((word_index + 1))\n                        word_matched=1\n                        break\n                    fi\n                fi\n            done\n            if [[ $word_matched -ne 0 ]]; then\n                continue\n            fi\n        fi\n\n        if [[ -v \"match_anything_transitions[$state]\" ]]; then\n            state=${match_anything_transitions[$state]}\n            word_index=$((word_index + 1))\n            continue\n        fi\n\n        return 1\n    done\n\n\n    local -a matches=()\n\n    local prefix=\"${words[$cword]}\"\n    if [[ -v \"literal_transitions[$state]\" ]]; then\n        local state_transitions_initializer=${literal_transitions[$state]}\n        declare -A state_transitions\n        eval \"state_transitions=$state_transitions_initializer\"\n\n        for literal_id in \"${!state_transitions[@]}\"; do\n            local literal=\"${literals[$literal_id]}\"\n            if [[ $literal = \"${prefix}\"* ]]; then\n                matches+=(\"$literal \")\n            fi\n        done\n    fi\n    declare -A commands\n    commands=([7]=0 [22]=1 [8]=3 [5]=2)\n    if [[ -v \"commands[$state]\" ]]; then\n        local command_id=${commands[$state]}\n        local completions=()\n        readarray -t completions < <(_hyprctl_cmd_${command_id} \"$prefix\" | cut -f1)\n        for item in \"${completions[@]}\"; do\n            if [[ $item = \"${prefix}\"* ]]; then\n                matches+=(\"$item\")\n            fi\n        done\n    fi\n\n\n    local shortest_suffix=\"$prefix\"\n    for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do\n        local char=\"${COMP_WORDBREAKS:$i:1}\"\n        local candidate=${prefix##*$char}\n        if [[ ${#candidate} -lt ${#shortest_suffix} ]]; then\n            shortest_suffix=$candidate\n        fi\n    done\n    local superfluous_prefix=\"\"\n    if [[ \"$shortest_suffix\" != \"$prefix\" ]]; then\n        local superfluous_prefix=${prefix%$shortest_suffix}\n    fi\n    COMPREPLY=(\"${matches[@]#$superfluous_prefix}\")\n\n    return 0\n}\n\ncomplete -o nospace -F _hyprctl hyprctl\n"
  },
  {
    "path": "hyprctl/hyprctl.fish",
    "content": "function _hyprctl_2\n    set 1 $argv[1]\n    hyprctl monitors | awk '/Monitor/{ print $2 }'\nend\n\nfunction _hyprctl_4\n    set 1 $argv[1]\n    hyprctl clients | awk '/class/{print $2}'\nend\n\nfunction _hyprctl_3\n    set 1 $argv[1]\n    hyprctl devices | sed -n '/Keyboard at/{n; s/^\\s\\+//; p}'\nend\n\nfunction _hyprctl_1\n    set 1 $argv[1]\n    hyprpm list | awk '/Plugin/{print $4}'\nend\n\nfunction _hyprctl\n    set COMP_LINE (commandline --cut-at-cursor)\n\n    set COMP_WORDS\n    echo $COMP_LINE | read --tokenize --array COMP_WORDS\n    if string match --quiet --regex '.*\\s$' $COMP_LINE\n        set COMP_CWORD (math (count $COMP_WORDS) + 1)\n    else\n        set COMP_CWORD (count $COMP_WORDS)\n    end\n\n    set literals \"resizeactive\" \"2\" \"changegroupactive\" \"-r\" \"moveintogroup\" \"forceallowsinput\" \"4\" \"::=\" \"systeminfo\" \"all\" \"layouts\" \"setprop\" \"animationstyle\" \"switchxkblayout\" \"create\" \"denywindowfromgroup\" \"headless\" \"activebordercolor\" \"exec\" \"setcursor\" \"wayland\" \"focusurgentorlast\" \"workspacerules\" \"movecurrentworkspacetomonitor\" \"movetoworkspacesilent\" \"hyprpaper\" \"alpha\" \"inactivebordercolor\" \"movegroupwindow\" \"movecursortocorner\" \"movewindowpixel\" \"prev\" \"movewindow\" \"globalshortcuts\" \"clients\" \"dimaround\" \"setignoregrouplock\" \"splash\" \"execr\" \"monitors\" \"0\" \"forcenoborder\" \"-q\" \"animations\" \"1\" \"nomaxsize\" \"splitratio\" \"moveactive\" \"pass\" \"swapnext\" \"devices\" \"layers\" \"rounding\" \"lockactivegroup\" \"5\" \"moveworkspacetomonitor\" \"-f\" \"-i\" \"--quiet\" \"forcenodim\" \"pin\" \"0\" \"1\" \"forceopaque\" \"forcenoshadow\" \"setfloating\" \"minsize\" \"alphaoverride\" \"sendshortcut\" \"workspaces\" \"cyclenext\" \"alterzorder\" \"togglegroup\" \"lockgroups\" \"bordersize\" \"dpms\" \"focuscurrentorlast\" \"-1\" \"--batch\" \"notify\" \"remove\" \"instances\" \"1\" \"3\" \"moveoutofgroup\" \"killactive\" \"2\" \"movetoworkspace\" \"movecursor\" \"configerrors\" \"closewindow\" \"swapwindow\" \"tagwindow\" \"forcerendererreload\" \"centerwindow\" \"auto\" \"focuswindow\" \"seterror\" \"nofocus\" \"alphafullscreen\" \"binds\" \"version\" \"-h\" \"togglespecialworkspace\" \"fullscreen\" \"windowdancecompat\" \"0\" \"keyword\" \"toggleopaque\" \"3\" \"--instance\" \"togglefloating\" \"renameworkspace\" \"alphafullscreenoverride\" \"activeworkspace\" \"x11\" \"kill\" \"forceopaqueoverriden\" \"output\" \"global\" \"dispatch\" \"reload\" \"forcenoblur\" \"-j\" \"event\" \"--help\" \"disable\" \"-1\" \"activewindow\" \"keepaspectratio\" \"dismissnotify\" \"focusmonitor\" \"movefocus\" \"plugin\" \"exit\" \"workspace\" \"fullscreenstate\" \"getoption\" \"alphainactiveoverride\" \"alphainactive\" \"decorations\" \"settiled\" \"config-only\" \"descriptions\" \"resizewindowpixel\" \"fakefullscreen\" \"rollinglog\" \"swapactiveworkspaces\" \"submap\" \"next\" \"movewindoworgroup\" \"cursorpos\" \"forcenoanims\" \"focusworkspaceoncurrentmonitor\" \"maxsize\" \"sendkeystate\"\n\n    set descriptions\n    set descriptions[1] \"Resize the active window\"\n    set descriptions[2] \"Fullscreen\"\n    set descriptions[3] \"Switch to the next window in a group\"\n    set descriptions[4] \"Refresh state after issuing the command\"\n    set descriptions[5] \"Move the active window into a group\"\n    set descriptions[7] \"CONFUSED\"\n    set descriptions[9] \"Print system info\"\n    set descriptions[11] \"List all layouts available (including plugin ones)\"\n    set descriptions[12] \"Set a property of a window\"\n    set descriptions[14] \"Set the xkb layout index for a keyboard\"\n    set descriptions[16] \"Prohibit the active window from becoming or being inserted into group\"\n    set descriptions[19] \"Execute a shell command\"\n    set descriptions[20] \"Set the cursor theme and reloads the cursor manager\"\n    set descriptions[22] \"Focus the urgent window or the last window\"\n    set descriptions[23] \"Get the list of defined workspace rules\"\n    set descriptions[24] \"Move the active workspace to a monitor\"\n    set descriptions[25] \"Move window doesn't switch to the workspace\"\n    set descriptions[26] \"Interact with hyprpaper if present\"\n    set descriptions[29] \"Swap the active window with the next or previous in a group\"\n    set descriptions[30] \"Move the cursor to the corner of the active window\"\n    set descriptions[31] \"Move a selected window\"\n    set descriptions[33] \"Move the active window in a direction or to a monitor\"\n    set descriptions[34] \"Lists all global shortcuts\"\n    set descriptions[35] \"List all windows with their properties\"\n    set descriptions[37] \"Temporarily enable or disable binds:ignore_group_lock\"\n    set descriptions[38] \"Print the current random splash\"\n    set descriptions[39] \"Execute a raw shell command\"\n    set descriptions[40] \"List active outputs with their properties\"\n    set descriptions[43] \"Disable output\"\n    set descriptions[44] \"Gets the current config info about animations and beziers\"\n    set descriptions[47] \"Change the split ratio\"\n    set descriptions[48] \"Move the active window\"\n    set descriptions[49] \"Pass the key to a specified window\"\n    set descriptions[50] \"Swap the focused window with the next window\"\n    set descriptions[51] \"List all connected keyboards and mice\"\n    set descriptions[52] \"List the layers\"\n    set descriptions[54] \"Lock the focused group\"\n    set descriptions[55] \"OK\"\n    set descriptions[56] \"Move a workspace to a monitor\"\n    set descriptions[58] \"Specify the Hyprland instance\"\n    set descriptions[59] \"Disable output\"\n    set descriptions[61] \"Pin a window\"\n    set descriptions[62] \"WARNING\"\n    set descriptions[63] \"INFO\"\n    set descriptions[66] \"Set the current window's floating state to true\"\n    set descriptions[69] \"On shortcut X sends shortcut Y to a specified window\"\n    set descriptions[70] \"List all workspaces with their properties\"\n    set descriptions[71] \"Focus the next window on a workspace\"\n    set descriptions[72] \"Modify the window stack order of the active or specified window\"\n    set descriptions[73] \"Toggle the current active window into a group\"\n    set descriptions[74] \"Lock the groups\"\n    set descriptions[76] \"Set all monitors' DPMS status\"\n    set descriptions[77] \"Switch focus from current to previously focused window\"\n    set descriptions[78] \"No Icon\"\n    set descriptions[79] \"Execute a batch of commands separated by ;\"\n    set descriptions[80] \"Send a notification using the built-in Hyprland notification system\"\n    set descriptions[82] \"List all running Hyprland instances and their info\"\n    set descriptions[83] \"Maximize no fullscreen\"\n    set descriptions[84] \"Maximize and fullscreen\"\n    set descriptions[85] \"Move the active window out of a group\"\n    set descriptions[86] \"Close the active window\"\n    set descriptions[87] \"HINT\"\n    set descriptions[88] \"Move the focused window to a workspace\"\n    set descriptions[89] \"Move the cursor to a specified position\"\n    set descriptions[90] \"List all current config parsing errors\"\n    set descriptions[91] \"Close a specified window\"\n    set descriptions[92] \"Swap the active window with another window\"\n    set descriptions[93] \"Apply a tag to the window\"\n    set descriptions[94] \"Force the renderer to reload all resources and outputs\"\n    set descriptions[95] \"Center the active window\"\n    set descriptions[97] \"Focus the first window matching\"\n    set descriptions[98] \"Set the hyprctl error string\"\n    set descriptions[101] \"List all registered binds\"\n    set descriptions[102] \"Print the Hyprland version: flags, commit and branch of build\"\n    set descriptions[103] \"Prints the help message\"\n    set descriptions[104] \"Toggle a special workspace on/off\"\n    set descriptions[105] \"Toggle the focused window's fullscreen state\"\n    set descriptions[107] \"None\"\n    set descriptions[108] \"Issue a keyword to call a config keyword dynamically\"\n    set descriptions[109] \"Toggle the current window to always be opaque\"\n    set descriptions[110] \"ERROR\"\n    set descriptions[111] \"Specify the Hyprland instance\"\n    set descriptions[112] \"Toggle the current window's floating state\"\n    set descriptions[113] \"Rename a workspace\"\n    set descriptions[115] \"Get the active workspace name and its properties\"\n    set descriptions[117] \"Get into a kill mode, where you can kill an app by clicking on it\"\n    set descriptions[119] \"Allows adding/removing fake outputs to a specific backend\"\n    set descriptions[120] \"Execute a Global Shortcut using the GlobalShortcuts portal\"\n    set descriptions[121] \"Issue a dispatch to call a keybind dispatcher with an arg\"\n    set descriptions[122] \"Force reload the config\"\n    set descriptions[124] \"Output in JSON format\"\n    set descriptions[125] \"Emits a custom event to socket2\"\n    set descriptions[126] \"Prints the help message\"\n    set descriptions[128] \"Current\"\n    set descriptions[129] \"Get the active window name and its properties\"\n    set descriptions[131] \"Dismiss all or up to amount of notifications\"\n    set descriptions[132] \"Focus a monitor\"\n    set descriptions[133] \"Move the focus in a direction\"\n    set descriptions[134] \"Interact with a plugin\"\n    set descriptions[135] \"Exit the compositor with no questions asked\"\n    set descriptions[136] \"Change the workspace\"\n    set descriptions[137] \"Sets the focused window’s fullscreen mode and the one sent to the client\"\n    set descriptions[138] \"Get the config option status (values)\"\n    set descriptions[141] \"List all decorations and their info\"\n    set descriptions[142] \"Set the current window's floating state to false\"\n    set descriptions[144] \"Return a parsable JSON with all the config options, descriptions, value types and ranges\"\n    set descriptions[145] \"Resize a selected window\"\n    set descriptions[146] \"Toggle the focused window's internal fullscreen state\"\n    set descriptions[147] \"Print tail of the log\"\n    set descriptions[148] \"Swap the active workspaces between two monitors\"\n    set descriptions[149] \"Change the current mapping group\"\n    set descriptions[151] \"Behave as moveintogroup\"\n    set descriptions[152] \"Get the current cursor pos in global layout coordinates\"\n    set descriptions[154] \"Focus the requested workspace\"\n\n    set literal_transitions\n    set literal_transitions[1] \"set inputs 121 44 126 82 4 52 51 129 90 59 9 11 12 131 14 98 102 103 134 101 138 23 20 141 26 144 108 147 70 34 35 79 115 38 152 117 122 124 40 43 80 119; set tos 15 3 22 3 22 3 3 3 3 22 3 3 4 5 6 7 3 22 8 3 3 3 3 9 3 3 10 11 3 3 3 22 3 3 3 3 14 22 12 22 16 13\"\n    set literal_transitions[2] \"set inputs 82 52 51 129 9 90 11 12 131 14 98 102 134 101 23 20 138 141 26 144 108 147 70 34 35 115 38 152 117 40 119 122 121 80 44; set tos 3 3 3 3 3 3 3 4 5 6 7 3 8 3 3 3 3 9 3 3 10 11 3 3 3 3 3 3 3 12 13 14 15 16 3\"\n    set literal_transitions[4] \"set inputs 140 64 65 46 106 28 27 53 6 67 68 130 114 13 75 100 36 153 99 60 118 42 18 139 155 123; set tos 3 17 17 17 17 3 3 5 17 3 17 17 17 3 5 3 17 17 17 17 17 17 3 17 3 17\"\n    set literal_transitions[7] \"set inputs 127; set tos 3\"\n    set literal_transitions[11] \"set inputs 57; set tos 3\"\n    set literal_transitions[12] \"set inputs 10; set tos 3\"\n    set literal_transitions[13] \"set inputs 15 81; set tos 20 23\"\n    set literal_transitions[14] \"set inputs 143; set tos 3\"\n    set literal_transitions[15] \"set inputs 1 85 3 86 5 88 89 91 92 93 94 95 97 16 19 104 22 105 24 25 29 30 31 109 112 33 113 37 39 120 125 47 48 49 50 54 56 132 133 135 136 61 137 142 66 145 146 69 148 71 72 73 74 149 76 77 151 154; set tos 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 21 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\"\n    set literal_transitions[16] \"set inputs 87 7 110 62 78 55 63; set tos 5 5 5 5 5 5 5\"\n    set literal_transitions[17] \"set inputs 41 45; set tos 3 3\"\n    set literal_transitions[18] \"set inputs 8; set tos 24\"\n    set literal_transitions[19] \"set inputs 32 150; set tos 3 3\"\n    set literal_transitions[20] \"set inputs 96 17 116 21; set tos 3 3 3 3\"\n    set literal_transitions[21] \"set inputs 107 83 128 2 84; set tos 3 3 3 3 3\"\n    set literal_transitions[24] \"set inputs 58 111; set tos 22 22\"\n\n    set match_anything_transitions_from 7 8 1 23 6 5 3 19 12 9 10 14 11 2\n    set match_anything_transitions_to 18 3 2 3 19 3 18 3 18 3 3 18 18 2\n\n    set state 1\n    set word_index 2\n    while test $word_index -lt $COMP_CWORD\n        set -- word $COMP_WORDS[$word_index]\n\n        if set --query literal_transitions[$state] && test -n $literal_transitions[$state]\n            set --erase inputs\n            set --erase tos\n            eval $literal_transitions[$state]\n\n            if contains -- $word $literals\n                set literal_matched 0\n                for literal_id in (seq 1 (count $literals))\n                    if test $literals[$literal_id] = $word\n                        set index (contains --index -- $literal_id $inputs)\n                        set state $tos[$index]\n                        set word_index (math $word_index + 1)\n                        set literal_matched 1\n                        break\n                    end\n                end\n                if test $literal_matched -ne 0\n                    continue\n                end\n            end\n        end\n\n        if set --query match_anything_transitions_from[$state] && test -n $match_anything_transitions_from[$state]\n            set index (contains --index -- $state $match_anything_transitions_from)\n            set state $match_anything_transitions_to[$index]\n            set word_index (math $word_index + 1)\n            continue\n        end\n\n        return 1\n    end\n\n    if set --query literal_transitions[$state] && test -n $literal_transitions[$state]\n        set --erase inputs\n        set --erase tos\n        eval $literal_transitions[$state]\n        for literal_id in $inputs\n            if test -n $descriptions[$literal_id]\n                printf '%s\\t%s\\n' $literals[$literal_id] $descriptions[$literal_id]\n            else\n                printf '%s\\n' $literals[$literal_id]\n            end\n        end\n    end\n\n    set command_states 8 23 9 6\n    set command_ids 1 2 4 3\n    if contains $state $command_states\n        set index (contains --index $state $command_states)\n        set function_id $command_ids[$index]\n        set function_name _hyprctl_$function_id\n        set --erase inputs\n        set --erase tos\n        $function_name \"$COMP_WORDS[$COMP_CWORD]\"\n    end\n\n    return 0\nend\n\ncomplete --command hyprctl --no-files --arguments \"(_hyprctl)\"\n"
  },
  {
    "path": "hyprctl/hyprctl.usage",
    "content": "# This is a file fed to complgen to generate bash/fish/zsh completions\n# Repo: https://github.com/adaszko/complgen\n# Generate completion scripts: \"complgen aot --bash-script hyprctl.bash --fish-script hyprctl.fish --zsh-script hyprctl.zsh ./hyprctl.usage\"\n\nhyprctl [<OPTIONS>]... <ARGUMENTS>\n\n<OPTIONS>  ::=  (-i | --instance)                                     \"Specify the Hyprland instance\"\n            |   (-j)                                                  \"Output in JSON format\"\n            |   (-r)                                                  \"Refresh state after issuing the command\"\n            |   (--batch)                                             \"Execute a batch of commands separated by ;\"\n            |   (-q | --quiet)                                        \"Disable output\"\n            |   (-h | --help)                                         \"Prints the help message\"\n            ;\n\n<WINDOWS> ::= {{{ hyprctl clients | awk '/class/{print $2}' }}};\n\n<AVAILABLE_PLUGINS> ::= {{{ hyprpm list | awk '/Plugin/{print $4}' }}};\n\n<MONITORS> ::= {{{ hyprctl monitors | awk '/Monitor/{ print $2 }' }}};\n\n<KEYBOARDS> ::= {{{ hyprctl devices | sed -n '/Keyboard at/{n; s/^\\s\\+//; p}' }}};\n\n<NOTIFICATION_TYPES> ::=  (0)   \"WARNING\"\n                      |   (1)   \"INFO\"\n                      |   (2)   \"HINT\"\n                      |   (3)   \"ERROR\"\n                      |   (4)   \"CONFUSED\"\n                      |   (5)   \"OK\"\n                      |   (-1)  \"No Icon\"\n                      ;\n\n<PROPS> ::=  (animationstyle)\n         |   (rounding <NUM>)\n         |   (bordersize <NUM>)\n         |   (forcenoblur (0 | 1))\n         |   (forceopaque (0 | 1))\n         |   (forceopaqueoverriden (0 | 1))\n         |   (forceallowsinput (0 | 1))\n         |   (forcenoanims (0 | 1))\n         |   (forcenoborder (0 | 1))\n         |   (forcenodim (0 | 1))\n         |   (forcenoshadow (0 | 1))\n         |   (nofocus (0 | 1))\n         |   (windowdancecompat (0 | 1))\n         |   (nomaxsize (0 | 1))\n         |   (minsize)\n         |   (maxsize)\n         |   (dimaround (0 | 1))\n         |   (keepaspectratio (0 | 1))\n         |   (alphaoverride (0 | 1))\n         |   (alpha)\n         |   (alphainactiveoverride (0 | 1))\n         |   (alphainactive)\n         |   (alphafullscreenoverride (0 | 1))\n         |   (alphafullscreen)\n         |   (activebordercolor)\n         |   (inactivebordercolor)\n         ;\n\n\n<ARGUMENTS> ::= (activewindow)                                        \"Get the active window name and its properties\"\n            |   (activeworkspace)                                     \"Get the active workspace name and its properties\"\n            |   (animations)                                          \"Gets the current config info about animations and beziers\"\n            |   (binds)                                               \"List all registered binds\"\n            |   (clients)                                             \"List all windows with their properties\"\n            |   (configerrors)                                        \"List all current config parsing errors\"\n            |   (cursorpos)                                           \"Get the current cursor pos in global layout coordinates\"\n            |   (decorations <WINDOWS>)                               \"List all decorations and their info\"\n            |   (descriptions)                                        \"Return a parsable JSON with all the config options, descriptions, value types and ranges\"\n            |   (devices)                                             \"List all connected keyboards and mice\"\n            |   (dismissnotify <NUM>)                                 \"Dismiss all or up to amount of notifications\"\n            |   (dispatch <DISPATCHERS>)                              \"Issue a dispatch to call a keybind dispatcher with an arg\"\n            |   (getoption)                                           \"Get the config option status (values)\"\n            |   (globalshortcuts)                                     \"Lists all global shortcuts\"\n            |   (hyprpaper)                                           \"Interact with hyprpaper if present\"\n            |   (instances)                                           \"List all running Hyprland instances and their info\"\n            |   (keyword <KEYWORDS>)                                  \"Issue a keyword to call a config keyword dynamically\"\n            |   (kill)                                                \"Get into a kill mode, where you can kill an app by clicking on it\"\n            |   (layers)                                              \"List the layers\"\n            |   (layouts)                                             \"List all layouts available (including plugin ones)\"\n            |   (monitors [all])                                      \"List active outputs with their properties\"\n            |   (notify <NOTIFICATION_TYPES> <NUM>)                   \"Send a notification using the built-in Hyprland notification system\"\n            |   (output (create (wayland | x11 | headless | auto) | remove <MONITORS>)) \"Allows adding/removing fake outputs to a specific backend\"\n            |   (plugin <AVAILABLE_PLUGINS>)                          \"Interact with a plugin\"\n            |   (reload [config-only])                                \"Force reload the config\"\n            |   (rollinglog [-f])                                     \"Print tail of the log\"\n            |   (setcursor)                                           \"Set the cursor theme and reloads the cursor manager\"\n            |   (seterror [disable])                                  \"Set the hyprctl error string\"\n            |   (setprop <PROPS>)                                     \"Set a property of a window\"\n            |   (splash)                                              \"Print the current random splash\"\n            |   (switchxkblayout <KEYBOARDS> (next | prev | <NUM>))   \"Set the xkb layout index for a keyboard\"\n            |   (systeminfo)                                          \"Print system info\"\n            |   (version)                                             \"Print the Hyprland version: flags, commit and branch of build\"\n            |   (workspacerules)                                      \"Get the list of defined workspace rules\"\n            |   (workspaces)                                          \"List all workspaces with their properties\"\n            ;\n\n<WINDOW_STATE> ::=  (-1)    \"Current\"\n                |   (0)     \"None\"\n                |   (1)     \"Maximize no fullscreen\"\n                |   (2)     \"Fullscreen\"\n                |   (3)     \"Maximize and fullscreen\"\n                ;\n\n<DISPATCHERS> ::=  (exec)         \t                                  \"Execute a shell command\"\n                |  (execr)           \t                              \"Execute a raw shell command\"\n                |  (pass)            \t                              \"Pass the key to a specified window\"\n                |  (sendshortcut)            \t                      \"On shortcut X sends shortcut Y to a specified window\"\n                |  (sendkeystate)            \t                      \"Send a key with specific state (down/repeat/up) to a specified window (window must keep focus for events to continue)\"\n                |  (killactive)          \t                          \"Close the active window\"\n                |  (closewindow)         \t                          \"Close a specified window\"\n                |  (workspace)           \t                          \"Change the workspace\"\n                |  (movetoworkspace)         \t                      \"Move the focused window to a workspace\"\n                |  (movetoworkspacesilent)           \t              \"Move window doesn't switch to the workspace\"\n                |  (togglefloating)          \t                      \"Toggle the current window's floating state\"\n                |  (setfloating)         \t                          \"Set the current window's floating state to true\"\n                |  (settiled)            \t                          \"Set the current window's floating state to false\"\n                |  (fullscreen)          \t                          \"Toggle the focused window's fullscreen state\"\n                |  (fakefullscreen)          \t                      \"Toggle the focused window's internal fullscreen state\"\n                |  (fullscreenstate <WINDOW_STATE>)                   \"Sets the focused window’s fullscreen mode and the one sent to the client\"\n                |  (dpms)            \t                              \"Set all monitors' DPMS status\"\n                |  (pin)         \t                                  \"Pin a window\"\n                |  (movefocus)           \t                          \"Move the focus in a direction\"\n                |  (movewindow)          \t                          \"Move the active window in a direction or to a monitor\"\n                |  (swapwindow)          \t                          \"Swap the active window with another window\"\n                |  (centerwindow)            \t                      \"Center the active window\"\n                |  (resizeactive)            \t                      \"Resize the active window\"\n                |  (moveactive)          \t                          \"Move the active window\"\n                |  (resizewindowpixel)           \t                  \"Resize a selected window\"\n                |  (movewindowpixel)         \t                      \"Move a selected window\"\n                |  (cyclenext)           \t                          \"Focus the next window on a workspace\"\n                |  (swapnext)            \t                          \"Swap the focused window with the next window\"\n                |  (tagwindow)           \t                          \"Apply a tag to the window\"\n                |  (focuswindow)         \t                          \"Focus the first window matching\"\n                |  (focusmonitor)            \t                      \"Focus a monitor\"\n                |  (splitratio)          \t                          \"Change the split ratio\"\n                |  (toggleopaque)            \t                      \"Toggle the current window to always be opaque\"\n                |  (movecursortocorner)          \t                  \"Move the cursor to the corner of the active window\"\n                |  (movecursor)          \t                          \"Move the cursor to a specified position\"\n                |  (renameworkspace)         \t                      \"Rename a workspace\"\n                |  (exit)            \t                              \"Exit the compositor with no questions asked\"\n                |  (forcerendererreload)         \t                  \"Force the renderer to reload all resources and outputs\"\n                |  (movecurrentworkspacetomonitor)           \t      \"Move the active workspace to a monitor\"\n                |  (focusworkspaceoncurrentmonitor)          \t      \"Focus the requested workspace\"\n                |  (moveworkspacetomonitor)          \t              \"Move a workspace to a monitor\"\n                |  (swapactiveworkspaces)            \t              \"Swap the active workspaces between two monitors\"\n                |  (alterzorder)         \t                          \"Modify the window stack order of the active or specified window\"\n                |  (togglespecialworkspace)          \t              \"Toggle a special workspace on/off\"\n                |  (focusurgentorlast)           \t                  \"Focus the urgent window or the last window\"\n                |  (togglegroup)         \t                          \"Toggle the current active window into a group\"\n                |  (changegroupactive)           \t                  \"Switch to the next window in a group\"\n                |  (focuscurrentorlast)          \t                  \"Switch focus from current to previously focused window\"\n                |  (lockgroups)          \t                          \"Lock the groups\"\n                |  (lockactivegroup)         \t                      \"Lock the focused group\"\n                |  (moveintogroup)           \t                      \"Move the active window into a group\"\n                |  (moveoutofgroup)          \t                      \"Move the active window out of a group\"\n                |  (movewindoworgroup)           \t                  \"Behave as moveintogroup\"\n                |  (movegroupwindow)         \t                      \"Swap the active window with the next or previous in a group\"\n                |  (denywindowfromgroup)         \t                  \"Prohibit the active window from becoming or being inserted into group\"\n                |  (setignoregrouplock)          \t                  \"Temporarily enable or disable binds:ignore_group_lock\"\n                |  (global)          \t                              \"Execute a Global Shortcut using the GlobalShortcuts portal\"\n                |  (submap)          \t                              \"Change the current mapping group\"\n                |  (event)                                            \"Emits a custom event to socket2\"\n                ;\n"
  },
  {
    "path": "hyprctl/hyprctl.zsh",
    "content": "#compdef hyprctl\n\n_hyprctl_cmd_1 () {\n    hyprctl monitors | awk '/Monitor/{ print $2 }'\n}\n\n_hyprctl_cmd_3 () {\n    hyprctl clients | awk '/class/{print $2}'\n}\n\n_hyprctl_cmd_2 () {\n    hyprctl devices | sed -n '/Keyboard at/{n; s/^\\s\\+//; p}'\n}\n\n_hyprctl_cmd_0 () {\n    hyprpm list | awk '/Plugin/{print $4}'\n}\n\n_hyprctl () {\n    local -a literals=(\"resizeactive\" \"2\" \"changegroupactive\" \"-r\" \"moveintogroup\" \"forceallowsinput\" \"4\" \"::=\" \"systeminfo\" \"all\" \"layouts\" \"setprop\" \"animationstyle\" \"switchxkblayout\" \"create\" \"denywindowfromgroup\" \"headless\" \"activebordercolor\" \"exec\" \"setcursor\" \"wayland\" \"focusurgentorlast\" \"workspacerules\" \"movecurrentworkspacetomonitor\" \"movetoworkspacesilent\" \"hyprpaper\" \"alpha\" \"inactivebordercolor\" \"movegroupwindow\" \"movecursortocorner\" \"movewindowpixel\" \"prev\" \"movewindow\" \"globalshortcuts\" \"clients\" \"dimaround\" \"setignoregrouplock\" \"splash\" \"execr\" \"monitors\" \"0\" \"forcenoborder\" \"-q\" \"animations\" \"1\" \"nomaxsize\" \"splitratio\" \"moveactive\" \"pass\" \"swapnext\" \"devices\" \"layers\" \"rounding\" \"lockactivegroup\" \"5\" \"moveworkspacetomonitor\" \"-f\" \"-i\" \"--quiet\" \"forcenodim\" \"pin\" \"0\" \"1\" \"forceopaque\" \"forcenoshadow\" \"setfloating\" \"minsize\" \"alphaoverride\" \"sendshortcut\" \"workspaces\" \"cyclenext\" \"alterzorder\" \"togglegroup\" \"lockgroups\" \"bordersize\" \"dpms\" \"focuscurrentorlast\" \"-1\" \"--batch\" \"notify\" \"remove\" \"instances\" \"1\" \"3\" \"moveoutofgroup\" \"killactive\" \"2\" \"movetoworkspace\" \"movecursor\" \"configerrors\" \"closewindow\" \"swapwindow\" \"tagwindow\" \"forcerendererreload\" \"centerwindow\" \"auto\" \"focuswindow\" \"seterror\" \"nofocus\" \"alphafullscreen\" \"binds\" \"version\" \"-h\" \"togglespecialworkspace\" \"fullscreen\" \"windowdancecompat\" \"0\" \"keyword\" \"toggleopaque\" \"3\" \"--instance\" \"togglefloating\" \"renameworkspace\" \"alphafullscreenoverride\" \"activeworkspace\" \"x11\" \"kill\" \"forceopaqueoverriden\" \"output\" \"global\" \"dispatch\" \"reload\" \"forcenoblur\" \"-j\" \"event\" \"--help\" \"disable\" \"-1\" \"activewindow\" \"keepaspectratio\" \"dismissnotify\" \"focusmonitor\" \"movefocus\" \"plugin\" \"exit\" \"workspace\" \"fullscreenstate\" \"getoption\" \"alphainactiveoverride\" \"alphainactive\" \"decorations\" \"settiled\" \"config-only\" \"descriptions\" \"resizewindowpixel\" \"fakefullscreen\" \"rollinglog\" \"swapactiveworkspaces\" \"submap\" \"next\" \"movewindoworgroup\" \"cursorpos\" \"forcenoanims\" \"focusworkspaceoncurrentmonitor\" \"maxsize\" \"sendkeystate\")\n\n    local -A descriptions\n    descriptions[1]=\"Resize the active window\"\n    descriptions[2]=\"Fullscreen\"\n    descriptions[3]=\"Switch to the next window in a group\"\n    descriptions[4]=\"Refresh state after issuing the command\"\n    descriptions[5]=\"Move the active window into a group\"\n    descriptions[7]=\"CONFUSED\"\n    descriptions[9]=\"Print system info\"\n    descriptions[11]=\"List all layouts available (including plugin ones)\"\n    descriptions[12]=\"Set a property of a window\"\n    descriptions[14]=\"Set the xkb layout index for a keyboard\"\n    descriptions[16]=\"Prohibit the active window from becoming or being inserted into group\"\n    descriptions[19]=\"Execute a shell command\"\n    descriptions[20]=\"Set the cursor theme and reloads the cursor manager\"\n    descriptions[22]=\"Focus the urgent window or the last window\"\n    descriptions[23]=\"Get the list of defined workspace rules\"\n    descriptions[24]=\"Move the active workspace to a monitor\"\n    descriptions[25]=\"Move window doesn't switch to the workspace\"\n    descriptions[26]=\"Interact with hyprpaper if present\"\n    descriptions[29]=\"Swap the active window with the next or previous in a group\"\n    descriptions[30]=\"Move the cursor to the corner of the active window\"\n    descriptions[31]=\"Move a selected window\"\n    descriptions[33]=\"Move the active window in a direction or to a monitor\"\n    descriptions[34]=\"Lists all global shortcuts\"\n    descriptions[35]=\"List all windows with their properties\"\n    descriptions[37]=\"Temporarily enable or disable binds:ignore_group_lock\"\n    descriptions[38]=\"Print the current random splash\"\n    descriptions[39]=\"Execute a raw shell command\"\n    descriptions[40]=\"List active outputs with their properties\"\n    descriptions[43]=\"Disable output\"\n    descriptions[44]=\"Gets the current config info about animations and beziers\"\n    descriptions[47]=\"Change the split ratio\"\n    descriptions[48]=\"Move the active window\"\n    descriptions[49]=\"Pass the key to a specified window\"\n    descriptions[50]=\"Swap the focused window with the next window\"\n    descriptions[51]=\"List all connected keyboards and mice\"\n    descriptions[52]=\"List the layers\"\n    descriptions[54]=\"Lock the focused group\"\n    descriptions[55]=\"OK\"\n    descriptions[56]=\"Move a workspace to a monitor\"\n    descriptions[58]=\"Specify the Hyprland instance\"\n    descriptions[59]=\"Disable output\"\n    descriptions[61]=\"Pin a window\"\n    descriptions[62]=\"WARNING\"\n    descriptions[63]=\"INFO\"\n    descriptions[66]=\"Set the current window's floating state to true\"\n    descriptions[69]=\"On shortcut X sends shortcut Y to a specified window\"\n    descriptions[70]=\"List all workspaces with their properties\"\n    descriptions[71]=\"Focus the next window on a workspace\"\n    descriptions[72]=\"Modify the window stack order of the active or specified window\"\n    descriptions[73]=\"Toggle the current active window into a group\"\n    descriptions[74]=\"Lock the groups\"\n    descriptions[76]=\"Set all monitors' DPMS status\"\n    descriptions[77]=\"Switch focus from current to previously focused window\"\n    descriptions[78]=\"No Icon\"\n    descriptions[79]=\"Execute a batch of commands separated by ;\"\n    descriptions[80]=\"Send a notification using the built-in Hyprland notification system\"\n    descriptions[82]=\"List all running Hyprland instances and their info\"\n    descriptions[83]=\"Maximize no fullscreen\"\n    descriptions[84]=\"Maximize and fullscreen\"\n    descriptions[85]=\"Move the active window out of a group\"\n    descriptions[86]=\"Close the active window\"\n    descriptions[87]=\"HINT\"\n    descriptions[88]=\"Move the focused window to a workspace\"\n    descriptions[89]=\"Move the cursor to a specified position\"\n    descriptions[90]=\"List all current config parsing errors\"\n    descriptions[91]=\"Close a specified window\"\n    descriptions[92]=\"Swap the active window with another window\"\n    descriptions[93]=\"Apply a tag to the window\"\n    descriptions[94]=\"Force the renderer to reload all resources and outputs\"\n    descriptions[95]=\"Center the active window\"\n    descriptions[97]=\"Focus the first window matching\"\n    descriptions[98]=\"Set the hyprctl error string\"\n    descriptions[101]=\"List all registered binds\"\n    descriptions[102]=\"Print the Hyprland version: flags, commit and branch of build\"\n    descriptions[103]=\"Prints the help message\"\n    descriptions[104]=\"Toggle a special workspace on/off\"\n    descriptions[105]=\"Toggle the focused window's fullscreen state\"\n    descriptions[107]=\"None\"\n    descriptions[108]=\"Issue a keyword to call a config keyword dynamically\"\n    descriptions[109]=\"Toggle the current window to always be opaque\"\n    descriptions[110]=\"ERROR\"\n    descriptions[111]=\"Specify the Hyprland instance\"\n    descriptions[112]=\"Toggle the current window's floating state\"\n    descriptions[113]=\"Rename a workspace\"\n    descriptions[115]=\"Get the active workspace name and its properties\"\n    descriptions[117]=\"Get into a kill mode, where you can kill an app by clicking on it\"\n    descriptions[119]=\"Allows adding/removing fake outputs to a specific backend\"\n    descriptions[120]=\"Execute a Global Shortcut using the GlobalShortcuts portal\"\n    descriptions[121]=\"Issue a dispatch to call a keybind dispatcher with an arg\"\n    descriptions[122]=\"Force reload the config\"\n    descriptions[124]=\"Output in JSON format\"\n    descriptions[125]=\"Emits a custom event to socket2\"\n    descriptions[126]=\"Prints the help message\"\n    descriptions[128]=\"Current\"\n    descriptions[129]=\"Get the active window name and its properties\"\n    descriptions[131]=\"Dismiss all or up to amount of notifications\"\n    descriptions[132]=\"Focus a monitor\"\n    descriptions[133]=\"Move the focus in a direction\"\n    descriptions[134]=\"Interact with a plugin\"\n    descriptions[135]=\"Exit the compositor with no questions asked\"\n    descriptions[136]=\"Change the workspace\"\n    descriptions[137]=\"Sets the focused window’s fullscreen mode and the one sent to the client\"\n    descriptions[138]=\"Get the config option status (values)\"\n    descriptions[141]=\"List all decorations and their info\"\n    descriptions[142]=\"Set the current window's floating state to false\"\n    descriptions[144]=\"Return a parsable JSON with all the config options, descriptions, value types and ranges\"\n    descriptions[145]=\"Resize a selected window\"\n    descriptions[146]=\"Toggle the focused window's internal fullscreen state\"\n    descriptions[147]=\"Print tail of the log\"\n    descriptions[148]=\"Swap the active workspaces between two monitors\"\n    descriptions[149]=\"Change the current mapping group\"\n    descriptions[151]=\"Behave as moveintogroup\"\n    descriptions[152]=\"Get the current cursor pos in global layout coordinates\"\n    descriptions[154]=\"Focus the requested workspace\"\n\n    local -A literal_transitions\n    literal_transitions[1]=\"([121]=15 [44]=3 [126]=22 [82]=3 [4]=22 [52]=3 [51]=3 [129]=3 [90]=3 [59]=22 [9]=3 [11]=3 [12]=4 [131]=5 [14]=6 [98]=7 [102]=3 [103]=22 [134]=8 [101]=3 [138]=3 [23]=3 [20]=3 [141]=9 [26]=3 [144]=3 [108]=10 [147]=11 [70]=3 [34]=3 [35]=3 [79]=22 [115]=3 [38]=3 [152]=3 [117]=3 [122]=14 [124]=22 [40]=12 [43]=22 [80]=16 [119]=13)\"\n    literal_transitions[2]=\"([82]=3 [52]=3 [51]=3 [129]=3 [9]=3 [90]=3 [11]=3 [12]=4 [131]=5 [14]=6 [98]=7 [102]=3 [134]=8 [101]=3 [23]=3 [20]=3 [138]=3 [141]=9 [26]=3 [144]=3 [108]=10 [147]=11 [70]=3 [34]=3 [35]=3 [115]=3 [38]=3 [152]=3 [117]=3 [40]=12 [119]=13 [122]=14 [121]=15 [80]=16 [44]=3)\"\n    literal_transitions[4]=\"([140]=3 [64]=17 [65]=17 [46]=17 [106]=17 [28]=3 [27]=3 [53]=5 [6]=17 [67]=3 [68]=17 [130]=17 [114]=17 [13]=3 [75]=5 [100]=3 [36]=17 [153]=17 [99]=17 [60]=17 [118]=17 [42]=17 [18]=3 [139]=17 [155]=3 [123]=17)\"\n    literal_transitions[7]=\"([127]=3)\"\n    literal_transitions[11]=\"([57]=3)\"\n    literal_transitions[12]=\"([10]=3)\"\n    literal_transitions[13]=\"([15]=20 [81]=23)\"\n    literal_transitions[14]=\"([143]=3)\"\n    literal_transitions[15]=\"([1]=3 [85]=3 [3]=3 [86]=3 [5]=3 [88]=3 [89]=3 [91]=3 [92]=3 [93]=3 [94]=3 [95]=3 [97]=3 [16]=3 [19]=3 [104]=3 [22]=3 [105]=3 [24]=3 [25]=3 [29]=3 [30]=3 [31]=3 [109]=3 [112]=3 [33]=3 [113]=3 [37]=3 [39]=3 [120]=3 [125]=3 [47]=3 [48]=3 [49]=3 [50]=3 [54]=3 [56]=3 [132]=3 [133]=3 [135]=3 [136]=3 [61]=3 [137]=21 [142]=3 [66]=3 [145]=3 [146]=3 [69]=3 [148]=3 [71]=3 [72]=3 [73]=3 [74]=3 [149]=3 [76]=3 [77]=3 [151]=3 [154]=3)\"\n    literal_transitions[16]=\"([87]=5 [7]=5 [110]=5 [62]=5 [78]=5 [55]=5 [63]=5)\"\n    literal_transitions[17]=\"([41]=3 [45]=3)\"\n    literal_transitions[18]=\"([8]=24)\"\n    literal_transitions[19]=\"([32]=3 [150]=3)\"\n    literal_transitions[20]=\"([96]=3 [17]=3 [116]=3 [21]=3)\"\n    literal_transitions[21]=\"([107]=3 [83]=3 [128]=3 [2]=3 [84]=3)\"\n    literal_transitions[24]=\"([58]=22 [111]=22)\"\n\n    local -A match_anything_transitions\n    match_anything_transitions=([7]=18 [8]=3 [1]=2 [23]=3 [6]=19 [5]=3 [3]=18 [19]=3 [12]=18 [9]=3 [10]=3 [14]=18 [11]=18 [2]=2)\n\n    declare -A subword_transitions\n\n    local state=1\n    local word_index=2\n    while [[ $word_index -lt $CURRENT ]]; do\n        if [[ -v \"literal_transitions[$state]\" ]]; then\n            local -A state_transitions\n            eval \"state_transitions=${literal_transitions[$state]}\"\n\n            local word=${words[$word_index]}\n            local word_matched=0\n            for ((literal_id = 1; literal_id <= $#literals; literal_id++)); do\n                if [[ ${literals[$literal_id]} = \"$word\" ]]; then\n                    if [[ -v \"state_transitions[$literal_id]\" ]]; then\n                        state=${state_transitions[$literal_id]}\n                        word_index=$((word_index + 1))\n                        word_matched=1\n                        break\n                    fi\n                fi\n            done\n            if [[ $word_matched -ne 0 ]]; then\n                continue\n            fi\n        fi\n\n        if [[ -v \"match_anything_transitions[$state]\" ]]; then\n            state=${match_anything_transitions[$state]}\n            word_index=$((word_index + 1))\n            continue\n        fi\n\n        return 1\n    done\n\n    completions_no_description_trailing_space=()\n    completions_no_description_no_trailing_space=()\n    completions_trailing_space=()\n    suffixes_trailing_space=()\n    descriptions_trailing_space=()\n    completions_no_trailing_space=()\n    suffixes_no_trailing_space=()\n    descriptions_no_trailing_space=()\n\n    if [[ -v \"literal_transitions[$state]\" ]]; then\n        local -A state_transitions\n        eval \"state_transitions=${literal_transitions[$state]}\"\n\n        for literal_id in ${(k)state_transitions}; do\n            if [[ -v \"descriptions[$literal_id]\" ]]; then\n                completions_trailing_space+=(\"${literals[$literal_id]}\")\n                suffixes_trailing_space+=(\"${literals[$literal_id]}\")\n                descriptions_trailing_space+=(\"${descriptions[$literal_id]}\")\n            else\n                completions_no_description_trailing_space+=(\"${literals[$literal_id]}\")\n            fi\n        done\n    fi\n    local -A commands=([8]=0 [23]=1 [9]=3 [6]=2)\n\n    if [[ -v \"commands[$state]\" ]]; then\n        local command_id=${commands[$state]}\n        local output=$(_hyprctl_cmd_${command_id} \"${words[$CURRENT]}\")\n        local -a command_completions=(\"${(@f)output}\")\n        for line in ${command_completions[@]}; do\n            local parts=(${(@s:\t:)line})\n            if [[ -v \"parts[2]\" ]]; then\n                completions_trailing_space+=(\"${parts[1]}\")\n                suffixes_trailing_space+=(\"${parts[1]}\")\n                descriptions_trailing_space+=(\"${parts[2]}\")\n            else\n                completions_no_description_trailing_space+=(\"${parts[1]}\")\n            fi\n        done\n    fi\n\n    local maxlen=0\n    for suffix in ${suffixes_trailing_space[@]}; do\n        if [[ ${#suffix} -gt $maxlen ]]; then\n            maxlen=${#suffix}\n        fi\n    done\n    for suffix in ${suffixes_no_trailing_space[@]}; do\n        if [[ ${#suffix} -gt $maxlen ]]; then\n            maxlen=${#suffix}\n        fi\n    done\n\n    for ((i = 1; i <= $#suffixes_trailing_space; i++)); do\n        if [[ -z ${descriptions_trailing_space[$i]} ]]; then\n            descriptions_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_trailing_space[$i]}}\"\n        else\n            descriptions_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_trailing_space[$i]}} -- ${descriptions_trailing_space[$i]}\"\n        fi\n    done\n\n    for ((i = 1; i <= $#suffixes_no_trailing_space; i++)); do\n        if [[ -z ${descriptions_no_trailing_space[$i]} ]]; then\n            descriptions_no_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_no_trailing_space[$i]}}\"\n        else\n            descriptions_no_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_no_trailing_space[$i]}} -- ${descriptions_no_trailing_space[$i]}\"\n        fi\n    done\n\n    compadd -Q -a completions_no_description_trailing_space\n    compadd -Q -S ' ' -a completions_no_description_no_trailing_space\n    compadd -l -Q -a -d descriptions_trailing_space completions_trailing_space\n    compadd -l -Q -S '' -a -d descriptions_no_trailing_space completions_no_trailing_space\n    return 0\n}\n\nif [[ $ZSH_EVAL_CONTEXT =~ :file$ ]]; then\n    compdef _hyprctl hyprctl\nelse\n    _hyprctl\nfi\n"
  },
  {
    "path": "hyprctl/src/Strings.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\nconst std::string_view USAGE = R\"#(usage: hyprctl [flags] <command> [args...|--help]\n\ncommands:\n    activewindow        → Gets the active window name and its properties\n    activeworkspace     → Gets the active workspace and its properties\n    animations          → Gets the current config'd info about animations\n                          and beziers\n    binds               → Lists all registered binds\n    clients             → Lists all windows with their properties\n    configerrors        → Lists all current config parsing errors\n    cursorpos           → Gets the current cursor position in global layout\n                          coordinates\n    decorations <window_regex> → Lists all decorations and their info\n    devices             → Lists all connected keyboards and mice\n    dismissnotify [amount] → Dismisses all or up to AMOUNT notifications\n    dispatch <dispatcher> [args] → Issue a dispatch to call a keybind\n                          dispatcher with arguments\n    getoption <option>  → Gets the config option status (values)\n    globalshortcuts     → Lists all global shortcuts\n    hyprpaper ...       → Issue a hyprpaper request\n    hyprsunset ...      → Issue a hyprsunset request\n    instances           → Lists all running instances of Hyprland with\n                          their info\n    keyword <name> <value> → Issue a keyword to call a config keyword\n                          dynamically\n    kill                → Issue a kill to get into a kill mode, where you can\n                          kill an app by clicking on it. You can exit it\n                          with ESCAPE\n    layers              → Lists all the surface layers\n    layouts             → Lists all layouts available (including plugin'd ones)\n    monitors            → Lists active outputs with their properties,\n                          'monitors all' lists active and inactive outputs\n    notify ...          → Sends a notification using the built-in Hyprland\n                          notification system\n    output ...          → Allows you to add and remove fake outputs to your\n                          preferred backend\n    plugin ...          → Issue a plugin request\n    reload [config-only] → Issue a reload to force reload the config. Pass\n                          'config-only' to disable monitor reload\n    rollinglog          → Prints tail of the log. Also supports -f/--follow\n                          option\n    setcursor <theme> <size> → Sets the cursor theme and reloads the cursor\n                          manager\n    seterror <color> <message...> → Sets the hyprctl error string. Color has\n                          the same format as in colors in config. Will reset\n                          when Hyprland's config is reloaded\n    setprop ...         → Sets a window property\n    getprop ...         → Gets a window property\n    splash              → Get the current splash\n    switchxkblayout ... → Sets the xkb layout index for a keyboard\n    systeminfo          → Get system info\n    version             → Prints the hyprland version, meaning flags, commit\n                          and branch of build.\n    workspacerules      → Lists all workspace rules\n    workspaces          → Lists all workspaces with their properties\n\nflags:\n    -j                  → Output in JSON\n    -r                  → Refresh state after issuing command (e.g. for\n                          updating variables)\n    --batch             → Execute a batch of commands, separated by ';'\n    --instance (-i)     → use a specific instance. Can be either signature or\n                          index in hyprctl instances (0, 1, etc)\n    --quiet (-q)        → Disable the output of hyprctl\n\n--help:\n    Can be used to print command's arguments that did not fit into this page\n    (three dots))#\";\n\nconst std::string_view HYPRPAPER_HELP = R\"#(usage: hyprctl [flags] hyprpaper <request>\n\nrequests:\n    wallpaper       → Issue a wallpaper to call a config wallpaper dynamically.\n                      Arguments are [mon],[path],[fit_mode]. Fit mode is optional.\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view HYPRSUNSET_HELP = R\"#(usage: hyprctl [flags] hyprsunset <request>\n\nrequests:\n    temperature <temp> → Enable blue-light filter\n    identity           → Disable blue-light filter\n    gamma <gamma>      → Enable gamma filter\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view NOTIFY_HELP = R\"#(usage: hyprctl [flags] notify <icon> <time_ms> <color> <message...>\n\nicon:\n    Integer of value:\n        0       → Warning\n        1       → Info\n        2       → Hint\n        3       → Error\n        4       → Confused\n        5       → Ok\n        6 or -1 → No icon\n\ntime_ms:\n    Time to display notification in milliseconds\n\ncolor:\n    Notification color. Format is the same as for colors in hyprland.conf. Use\n    0 for default color for icon\n\nmessage:\n    Notification message\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view OUTPUT_HELP = R\"#(usage: hyprctl [flags] output <create <backend> | remove <name>>\n\ncreate <backend>:\n    Creates new virtual output. Possible values for backend: wayland, x11,\n    headless or auto.\n\nremove <name>:\n    Removes virtual output. Pass the output's name, as found in\n    'hyprctl monitors'\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view PLUGIN_HELP = R\"#(usage: hyprctl [flags] plugin <request>\n\nrequests:\n    load <path>     → Loads a plugin. Path must be absolute\n    unload <path>   → Unloads a plugin. Path must be absolute\n    list            → Lists all loaded plugins\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view SETPROP_HELP = R\"#(usage: hyprctl [flags] setprop <regex> <property> <value> [lock]\n\nregex:\n    Regular expression by which a window will be searched\n\nproperty:\n    See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list\n    of properties\n\nvalue:\n    Property value\n\nlock:\n    Optional argument. If lock is not added, will be unlocked. Locking means a\n    dynamic windowrule cannot override this setting.\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view GETPROP_HELP = R\"#(usage: hyprctl [flags] getprop <regex> <property>\n\nregex:\n    Regular expression by which a window will be searched\n\nproperty:\n    See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list\n    of properties\n\nflags:\n    See 'hyprctl --help')#\";\n\nconst std::string_view SWITCHXKBLAYOUT_HELP = R\"#(usage: [flags] switchxkblayout <device> <cmd>\n\ndevice:\n    You can find the device using 'hyprctl devices' command\n\ncmd:\n    'next' for next, 'prev' for previous, or ID for a specific one. IDs are\n    assigned based on their order in config file (keyboard_layout),\n    starting from 0\n\nflags:\n    See 'hyprctl --help')#\";\n"
  },
  {
    "path": "hyprctl/src/helpers/Memory.hpp",
    "content": "#pragma once\n\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/memory/UniquePtr.hpp>\n#include <hyprutils/memory/Atomic.hpp>\n\nusing namespace Hyprutils::Memory;\n\n#define SP CSharedPointer\n#define WP CWeakPointer\n#define UP CUniquePointer\n"
  },
  {
    "path": "hyprctl/src/hyprpaper/Hyprpaper.cpp",
    "content": "#include \"Hyprpaper.hpp\"\n#include \"../helpers/Memory.hpp\"\n\n#include <optional>\n#include <format>\n#include <filesystem>\n#include <print>\n\n#include <hyprpaper_core-client.hpp>\n\n#include <hyprutils/string/VarList2.hpp>\nusing namespace Hyprutils::String;\n\nusing namespace std::string_literals;\n\nconstexpr const char*          SOCKET_NAME = \".hyprpaper.sock\";\nstatic SP<CCHyprpaperCoreImpl> g_coreImpl;\n\nconstexpr const uint32_t       PROTOCOL_VERSION_SUPPORTED = 2;\n\n//\nstatic hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {\n    if (sv == \"contain\")\n        return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN;\n    if (sv == \"fit\" || sv == \"stretch\")\n        return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH;\n    if (sv == \"tile\")\n        return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE;\n    return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;\n}\n\nstatic std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {\n    std::error_code ec;\n    auto            can = std::filesystem::canonical(sv, ec);\n\n    if (ec)\n        return std::unexpected(std::format(\"invalid path: {}\", ec.message()));\n\n    return can;\n}\n\nstatic std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {\n    if (sv.empty())\n        return std::unexpected(\"empty path\");\n\n    if (sv[0] == '~') {\n        static auto HOME = getenv(\"HOME\");\n        if (!HOME || HOME[0] == '\\0')\n            return std::unexpected(\"home path but no $HOME\");\n\n        return resolvePath(std::string{HOME} + \"/\"s + std::string{sv.substr(1)});\n    }\n\n    return resolvePath(sv);\n}\n\nstatic std::expected<void, std::string> doWallpaper(const std::string_view& RHS) {\n    CVarList2         args(std::string{RHS}, 0, ',');\n\n    const std::string MONITOR  = std::string{args[0]};\n    const auto&       PATH_RAW = args[1];\n    const auto&       FIT      = args[2];\n\n    if (PATH_RAW.empty())\n        return std::unexpected(\"not enough args\");\n\n    const auto RTDIR = getenv(\"XDG_RUNTIME_DIR\");\n\n    if (!RTDIR || RTDIR[0] == '\\0')\n        return std::unexpected(\"can't send: no XDG_RUNTIME_DIR\");\n\n    const auto HIS = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n    if (!HIS || HIS[0] == '\\0')\n        return std::unexpected(\"can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)\");\n\n    const auto PATH = getFullPath(PATH_RAW);\n\n    if (!PATH)\n        return std::unexpected(std::format(\"bad path: {}\", PATH_RAW));\n\n    auto socketPath = RTDIR + \"/hypr/\"s + HIS + \"/\"s + SOCKET_NAME;\n\n    auto socket = Hyprwire::IClientSocket::open(socketPath);\n\n    if (!socket)\n        return std::unexpected(\"can't send: failed to connect to hyprpaper (is it running?)\");\n\n    g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED);\n\n    socket->addImplementation(g_coreImpl);\n\n    if (!socket->waitForHandshake())\n        return std::unexpected(\"can't send: wire handshake failed\");\n\n    auto spec = socket->getSpec(g_coreImpl->protocol()->specName());\n\n    if (!spec)\n        return std::unexpected(\"can't send: hyprpaper doesn't have the spec?!\");\n\n    auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));\n\n    if (!manager)\n        return std::unexpected(\"wire error: couldn't create manager\");\n\n    auto wallpaper = makeShared<CCHyprpaperWallpaperObject>(manager->sendGetWallpaperObject());\n\n    if (!wallpaper)\n        return std::unexpected(\"wire error: couldn't create wallpaper object\");\n\n    bool                       canExit = false;\n    std::optional<std::string> err;\n\n    wallpaper->setFailed([&canExit, &err](uint32_t code) {\n        canExit = true;\n        switch (code) {\n            case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format(\"failed to set wallpaper: Invalid path\", code); break;\n            case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format(\"failed to set wallpaper: Invalid monitor\", code); break;\n            default: err = std::format(\"failed to set wallpaper: unknown error, code {}\", code); break;\n        }\n    });\n    wallpaper->setSuccess([&canExit]() { canExit = true; });\n\n    wallpaper->sendPath(PATH->c_str());\n    wallpaper->sendMonitorName(MONITOR.c_str());\n    if (!FIT.empty())\n        wallpaper->sendFitMode(fitFromString(FIT));\n\n    wallpaper->sendApply();\n\n    while (!canExit) {\n        socket->dispatchEvents(true);\n    }\n\n    if (err)\n        return std::unexpected(*err);\n\n    return {};\n}\n\nstatic std::expected<void, std::string> doListActive() {\n    const auto RTDIR = getenv(\"XDG_RUNTIME_DIR\");\n\n    if (!RTDIR || RTDIR[0] == '\\0')\n        return std::unexpected(\"can't send: no XDG_RUNTIME_DIR\");\n\n    const auto HIS = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n    if (!HIS || HIS[0] == '\\0')\n        return std::unexpected(\"can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)\");\n\n    auto socketPath = RTDIR + \"/hypr/\"s + HIS + \"/\"s + SOCKET_NAME;\n\n    auto socket = Hyprwire::IClientSocket::open(socketPath);\n\n    if (!socket)\n        return std::unexpected(\"can't send: failed to connect to hyprpaper (is it running?)\");\n\n    g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED);\n\n    socket->addImplementation(g_coreImpl);\n\n    if (!socket->waitForHandshake())\n        return std::unexpected(\"can't send: wire handshake failed\");\n\n    auto spec = socket->getSpec(g_coreImpl->protocol()->specName());\n\n    if (!spec)\n        return std::unexpected(\"can't send: hyprpaper doesn't have the spec?!\");\n\n    if (spec->specVer() < 2)\n        return std::unexpected(\"can't send: hyprpaper protocol version too low (hyprpaper too old)\");\n\n    auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));\n\n    if (!manager)\n        return std::unexpected(\"wire error: couldn't create manager\");\n\n    auto status = makeShared<CCHyprpaperStatusObject>(manager->sendGetStatusObject());\n\n    status->setActiveWallpaper([](const char* mon, const char* wp) { std::println(\"{}: {}\", mon, wp); });\n\n    socket->roundtrip();\n\n    return {};\n}\n\nstd::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {\n    if (!rq.contains(' '))\n        return std::unexpected(\"Invalid request\");\n\n    if (!rq.starts_with(\"/hyprpaper \"))\n        return std::unexpected(\"Invalid request\");\n\n    std::string_view LHS, RHS;\n    auto             spacePos = rq.find(' ', 12);\n    LHS                       = rq.substr(11, spacePos - 11);\n    RHS                       = rq.substr(spacePos + 1);\n\n    if (LHS == \"wallpaper\")\n        return doWallpaper(RHS);\n    else if (LHS == \"listactive\")\n        return doListActive();\n    else\n        return std::unexpected(\"invalid hyprpaper request\");\n\n    return {};\n}\n"
  },
  {
    "path": "hyprctl/src/hyprpaper/Hyprpaper.hpp",
    "content": "#pragma once\n\n#include <expected>\n#include <string>\n\nnamespace Hyprpaper {\n    std::expected<void, std::string> makeHyprpaperRequest(const std::string_view& rq);\n};"
  },
  {
    "path": "hyprctl/src/main.cpp",
    "content": "#include <re2/re2.h>\n\n#include <cctype>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <pwd.h>\n#include <unistd.h>\n#include <algorithm>\n#include <csignal>\n#include <ranges>\n#include <optional>\n#include <charconv>\n\n#include <iostream>\n#include <string>\n#include <print>\n#include <fstream>\n#include <vector>\n#include <filesystem>\n#include <cstdarg>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/memory/Casts.hpp>\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Memory;\n\n#include \"Strings.hpp\"\n#include \"hyprpaper/Hyprpaper.hpp\"\n\nstd::string instanceSignature;\nbool        quiet = false;\n\nstruct SInstanceData {\n    std::string id;\n    uint64_t    time;\n    uint64_t    pid;\n    std::string wlSocket;\n};\n\nvoid log(const std::string_view str) {\n    if (quiet)\n        return;\n\n    std::println(\"{}\", str);\n}\n\nstatic int getUID() {\n    const auto UID   = getuid();\n    const auto PWUID = getpwuid(UID);\n    return PWUID ? PWUID->pw_uid : UID;\n}\n\nstd::string getRuntimeDir() {\n    const auto XDG = getenv(\"XDG_RUNTIME_DIR\");\n\n    if (!XDG) {\n        const std::string USERID = std::to_string(getUID());\n        return \"/run/user/\" + USERID + \"/hypr\";\n    }\n\n    return std::string{XDG} + \"/hypr\";\n}\n\nstatic std::optional<uint64_t> toUInt64(const std::string_view str) {\n    uint64_t value       = 0;\n    const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);\n    if (ec != std::errc() || ptr != str.data() + str.size())\n        return std::nullopt;\n    return value;\n}\n\nstatic std::optional<SInstanceData> parseInstance(const std::filesystem::directory_entry& entry) {\n    if (!entry.is_directory())\n        return std::nullopt;\n\n    const auto    lockPath = entry.path() / \"hyprland.lock\";\n    std::ifstream ifs(lockPath);\n    if (!ifs.is_open())\n        return std::nullopt;\n\n    SInstanceData data;\n    data.id = entry.path().filename().string();\n\n    const auto first = std::string_view{data.id}.find_first_of('_');\n    const auto last  = std::string_view{data.id}.find_last_of('_');\n    if (first == std::string_view::npos || last == std::string_view::npos || last <= first)\n        return std::nullopt;\n\n    auto time = toUInt64(std::string_view{data.id}.substr(first + 1, last - first - 1));\n    if (!time)\n        return std::nullopt;\n    data.time = *time;\n\n    std::string line;\n    if (!std::getline(ifs, line))\n        return std::nullopt;\n\n    auto pid = toUInt64(std::string_view{line});\n    if (!pid)\n        return std::nullopt;\n    data.pid = *pid;\n\n    if (!std::getline(ifs, data.wlSocket))\n        return std::nullopt;\n\n    if (std::getline(ifs, line) && !line.empty())\n        return std::nullopt; // more lines than expected\n\n    return data;\n}\n\nstd::vector<SInstanceData> instances() {\n    std::vector<SInstanceData> result;\n\n    std::error_code            ec;\n    const auto                 runtimeDir = getRuntimeDir();\n    if (!std::filesystem::exists(runtimeDir, ec) || ec)\n        return result;\n\n    std::filesystem::directory_iterator it(runtimeDir, std::filesystem::directory_options::skip_permission_denied, ec);\n    if (ec)\n        return result;\n\n    for (const auto& el : it) {\n        if (auto instance = parseInstance(el))\n            result.emplace_back(std::move(*instance));\n    }\n\n    std::erase_if(result, [](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });\n\n    std::ranges::sort(result, {}, &SInstanceData::time);\n\n    return result;\n}\n\nstatic volatile bool sigintReceived = false;\nvoid                 intHandler(int sig) {\n    sigintReceived = true;\n    std::println(\"[hyprctl] SIGINT received, closing connection\");\n}\n\nint rollingRead(const int socket) {\n    sigintReceived = false;\n    signal(SIGINT, intHandler);\n\n    constexpr size_t              BUFFER_SIZE = 8192;\n    std::array<char, BUFFER_SIZE> buffer      = {0};\n    long                          sizeWritten = 0;\n    std::println(\"[hyprctl] reading from socket following up log:\");\n    while (!sigintReceived) {\n        sizeWritten = read(socket, buffer.data(), BUFFER_SIZE);\n        if (sizeWritten < 0 && errno != EAGAIN) {\n            if (errno != EINTR)\n                std::println(\"Couldn't read (5): {}: {}\", strerror(errno), errno);\n            close(socket);\n            return 5;\n        }\n\n        if (sizeWritten == 0)\n            break;\n\n        if (sizeWritten > 0) {\n            std::println(\"{}\", std::string(buffer.data(), sizeWritten));\n            buffer.fill('\\0');\n        }\n\n        usleep(100000);\n    }\n    close(socket);\n    return 0;\n}\n\nint request(std::string_view arg, int minArgs = 0, bool needRoll = false) {\n    const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);\n\n    if (SERVERSOCKET < 0) {\n        log(\"Couldn't open a socket (1)\");\n        return 1;\n    }\n\n    auto t = timeval{.tv_sec = 5, .tv_usec = 0};\n    if (setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)) < 0) {\n        log(\"Couldn't set socket timeout (2)\");\n        return 2;\n    }\n\n    const auto ARGS = std::count(arg.begin(), arg.end(), ' ');\n\n    if (ARGS < minArgs) {\n        log(std::format(\"Not enough arguments in '{}', expected at least {}\", arg, minArgs));\n        return -1;\n    }\n\n    if (instanceSignature.empty()) {\n        log(\"HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)\");\n        return 3;\n    }\n\n    sockaddr_un serverAddress = {0};\n    serverAddress.sun_family  = AF_UNIX;\n\n    std::string socketPath = getRuntimeDir() + \"/\" + instanceSignature + \"/.socket.sock\";\n\n    strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);\n\n    if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {\n        log(\"Couldn't connect to \" + socketPath + \". (4)\");\n        return 4;\n    }\n\n    auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size());\n\n    if (sizeWritten < 0) {\n        log(\"Couldn't write (5)\");\n        return 5;\n    }\n\n    if (needRoll)\n        return rollingRead(SERVERSOCKET);\n\n    std::string      reply               = \"\";\n    constexpr size_t BUFFER_SIZE         = 8192;\n    char             buffer[BUFFER_SIZE] = {0};\n\n    // read all data until server closes the connection\n    // this handles partial writes on the server side under high load\n    while (true) {\n        sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);\n\n        if (sizeWritten < 0) {\n            if (errno == EWOULDBLOCK)\n                log(\"Hyprland IPC didn't respond in time\\n\");\n            log(\"Couldn't read (6)\");\n            return 6;\n        }\n\n        if (sizeWritten == 0) {\n            // server closed connection, we're done\n            break;\n        }\n\n        reply += std::string(buffer, sizeWritten);\n    }\n\n    close(SERVERSOCKET);\n\n    log(reply);\n\n    return 0;\n}\n\nint requestIPC(std::string_view filename, std::string_view arg) {\n    const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);\n\n    if (SERVERSOCKET < 0) {\n        log(\"Couldn't open a socket (1)\");\n        return 1;\n    }\n\n    if (instanceSignature.empty()) {\n        log(\"HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)\");\n        return 2;\n    }\n\n    sockaddr_un serverAddress = {0};\n    serverAddress.sun_family  = AF_UNIX;\n\n    std::string socketPath = getRuntimeDir() + \"/\" + instanceSignature + \"/\" + filename;\n\n    strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);\n\n    if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {\n        log(\"Couldn't connect to \" + socketPath + \". (3)\");\n        return 3;\n    }\n\n    arg = arg.substr(arg.find_first_of('/') + 1); // strip flags\n    arg = arg.substr(arg.find_first_of(' ') + 1); // strip \"hyprpaper\"\n\n    auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size());\n\n    if (sizeWritten < 0) {\n        log(\"Couldn't write (4)\");\n        return 4;\n    }\n    constexpr size_t BUFFER_SIZE         = 8192;\n    char             buffer[BUFFER_SIZE] = {0};\n\n    sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);\n\n    if (sizeWritten < 0) {\n        log(\"Couldn't read (5)\");\n        return 5;\n    }\n\n    close(SERVERSOCKET);\n\n    log(std::string(buffer));\n\n    return 0;\n}\n\nint requestHyprsunset(std::string_view arg) {\n    return requestIPC(\".hyprsunset.sock\", arg);\n}\n\nvoid batchRequest(std::string_view arg, bool json) {\n    std::string commands(arg.substr(arg.find_first_of(' ') + 1));\n\n    if (json) {\n        RE2::GlobalReplace(&commands, \";\\\\s*\", \";j/\");\n        commands.insert(0, \"j/\");\n    }\n\n    std::string rq = \"[[BATCH]]\" + commands;\n    request(rq);\n}\n\nvoid instancesRequest(bool json) {\n    std::string result = \"\";\n\n    // gather instance data\n    std::vector<SInstanceData> inst = instances();\n\n    if (!json) {\n        for (auto const& el : inst) {\n            result += std::format(\"instance {}:\\n\\ttime: {}\\n\\tpid: {}\\n\\twl socket: {}\\n\\n\", el.id, el.time, el.pid, el.wlSocket);\n        }\n    } else {\n        result += '[';\n        for (auto const& el : inst) {\n            result += std::format(R\"#(\n{{\n    \"instance\": \"{}\",\n    \"time\": {},\n    \"pid\": {},\n    \"wl_socket\": \"{}\"\n}},)#\",\n                                  el.id, el.time, el.pid, el.wlSocket);\n        }\n\n        result.pop_back();\n        result += \"\\n]\";\n    }\n\n    log(result + \"\\n\");\n}\n\nstd::vector<std::string> splitArgs(int argc, char** argv) {\n    std::vector<std::string> result;\n\n    for (auto i = 1 /* skip the executable */; i < argc; ++i)\n        result.emplace_back(argv[i]);\n\n    return result;\n}\n\nint main(int argc, char** argv) {\n    bool parseArgs = true;\n\n    if (argc < 2) {\n        std::println(\"{}\", USAGE);\n        return 1;\n    }\n\n    std::string fullRequest      = \"\";\n    std::string fullArgs         = \"\";\n    const auto  ARGS             = splitArgs(argc, argv);\n    bool        json             = false;\n    bool        needRoll         = false;\n    std::string overrideInstance = \"\";\n\n    for (std::size_t i = 0; i < ARGS.size(); ++i) {\n        if (ARGS[i] == \"--\") {\n            // Stop parsing arguments after --\n            parseArgs = false;\n            continue;\n        }\n        if (parseArgs && (ARGS[i][0] == '-') && !(isNumber(ARGS[i], true) || isNumber(ARGS[i].substr(0, ARGS[i].length() - 1), true)) /* For stuff like -2 or -2, */) {\n            // parse\n            if (ARGS[i] == \"-j\" && !fullArgs.contains(\"j\")) {\n                fullArgs += \"j\";\n                json = true;\n            } else if (ARGS[i] == \"-r\" && !fullArgs.contains(\"r\")) {\n                fullArgs += \"r\";\n            } else if (ARGS[i] == \"-a\" && !fullArgs.contains(\"a\")) {\n                fullArgs += \"a\";\n            } else if ((ARGS[i] == \"-c\" || ARGS[i] == \"--config\") && !fullArgs.contains(\"c\")) {\n                fullArgs += \"c\";\n            } else if ((ARGS[i] == \"-f\" || ARGS[i] == \"--follow\") && !fullArgs.contains(\"f\")) {\n                fullArgs += \"f\";\n                needRoll = true;\n            } else if (ARGS[i] == \"--batch\") {\n                fullRequest = \"--batch \";\n            } else if (ARGS[i] == \"--instance\" || ARGS[i] == \"-i\") {\n                ++i;\n\n                if (i >= ARGS.size()) {\n                    std::println(\"{}\", USAGE);\n                    return 1;\n                }\n\n                overrideInstance = ARGS[i];\n            } else if (ARGS[i] == \"-q\" || ARGS[i] == \"--quiet\") {\n                quiet = true;\n            } else if (ARGS[i] == \"--help\") {\n                const std::string& cmd = ARGS[0];\n\n                if (cmd == \"hyprpaper\") {\n                    std::println(\"{}\", HYPRPAPER_HELP);\n                } else if (cmd == \"hyprsunset\") {\n                    std::println(\"{}\", HYPRSUNSET_HELP);\n                } else if (cmd == \"notify\") {\n                    std::println(\"{}\", NOTIFY_HELP);\n                } else if (cmd == \"output\") {\n                    std::println(\"{}\", OUTPUT_HELP);\n                } else if (cmd == \"plugin\") {\n                    std::println(\"{}\", PLUGIN_HELP);\n                } else if (cmd == \"setprop\") {\n                    std::println(\"{}\", SETPROP_HELP);\n                } else if (cmd == \"getprop\") {\n                    std::println(\"{}\", GETPROP_HELP);\n                } else if (cmd == \"switchxkblayout\") {\n                    std::println(\"{}\", SWITCHXKBLAYOUT_HELP);\n                } else {\n                    std::println(\"{}\", USAGE);\n                }\n\n                return 1;\n            } else {\n                std::println(\"{}\", USAGE);\n                return 1;\n            }\n\n            continue;\n        }\n\n        fullRequest += ARGS[i] + \" \";\n    }\n\n    if (fullRequest.empty()) {\n        std::println(\"{}\", USAGE);\n        return 1;\n    }\n\n    fullRequest.pop_back(); // remove trailing space\n\n    fullRequest = fullArgs + \"/\" + fullRequest;\n\n    // instances is HIS-independent\n    if (fullRequest.contains(\"/instances\")) {\n        instancesRequest(json);\n        return 0;\n    }\n\n    if (needRoll && !fullRequest.contains(\"/rollinglog\")) {\n        log(\"only 'rollinglog' command supports '--follow' option\");\n        return 1;\n    }\n\n    if (overrideInstance.contains(\"_\"))\n        instanceSignature = overrideInstance;\n    else if (!overrideInstance.empty()) {\n        if (!isNumber(overrideInstance, false)) {\n            log(\"instance invalid\\n\");\n            return 1;\n        }\n\n        const auto INSTANCENO = std::stoi(overrideInstance);\n\n        const auto INSTANCES = instances();\n\n        if (INSTANCENO < 0 || sc<std::size_t>(INSTANCENO) >= INSTANCES.size()) {\n            log(\"no such instance\\n\");\n            return 1;\n        }\n\n        instanceSignature = INSTANCES[INSTANCENO].id;\n    } else {\n        const auto ISIG = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n        if (!ISIG) {\n            log(\"HYPRLAND_INSTANCE_SIGNATURE not set! (is hyprland running?)\\n\");\n            return 1;\n        }\n\n        instanceSignature = ISIG;\n    }\n\n    int exitStatus = 0;\n\n    if (fullRequest.contains(\"/--batch\"))\n        batchRequest(fullRequest, json);\n    else if (fullRequest.contains(\"/hyprpaper\")) {\n        auto result = Hyprpaper::makeHyprpaperRequest(fullRequest);\n        if (!result)\n            log(std::format(\"error: {}\", result.error()));\n        exitStatus = !result;\n    } else if (fullRequest.contains(\"/hyprsunset\"))\n        exitStatus = requestHyprsunset(fullRequest);\n    else if (fullRequest.contains(\"/switchxkblayout\"))\n        exitStatus = request(fullRequest, 2);\n    else if (fullRequest.contains(\"/seterror\"))\n        exitStatus = request(fullRequest, 1);\n    else if (fullRequest.contains(\"/setprop\"))\n        exitStatus = request(fullRequest, 3);\n    else if (fullRequest.contains(\"/plugin\"))\n        exitStatus = request(fullRequest, 1);\n    else if (fullRequest.contains(\"/dismissnotify\"))\n        exitStatus = request(fullRequest, 0);\n    else if (fullRequest.contains(\"/notify\"))\n        exitStatus = request(fullRequest, 2);\n    else if (fullRequest.contains(\"/output\"))\n        exitStatus = request(fullRequest, 2);\n    else if (fullRequest.contains(\"/setcursor\"))\n        exitStatus = request(fullRequest, 1);\n    else if (fullRequest.contains(\"/dispatch\"))\n        exitStatus = request(fullRequest, 1);\n    else if (fullRequest.contains(\"/keyword\"))\n        exitStatus = request(fullRequest, 2);\n    else if (fullRequest.contains(\"/decorations\"))\n        exitStatus = request(fullRequest, 1);\n    else if (fullRequest.contains(\"/--help\"))\n        std::println(\"{}\", USAGE);\n    else if (fullRequest.contains(\"/rollinglog\") && needRoll)\n        exitStatus = request(fullRequest, 0, true);\n    else {\n        exitStatus = request(fullRequest);\n    }\n\n    std::cout << std::flush;\n    return exitStatus;\n}\n"
  },
  {
    "path": "hyprland.pc.in",
    "content": "prefix=@PREFIX@/@INCLUDEDIR@\n\nName: Hyprland\nURL: https://github.com/hyprwm/Hyprland\nDescription: Hyprland header files\nVersion: @HYPRLAND_VERSION@\nRequires: aquamarine >= @AQUAMARINE_MINIMUM_VERSION@, hyprcursor >= @HYPRCURSOR_MINIMUM_VERSION@, hyprgraphics >= @HYPRGRAPHICS_MINIMUM_VERSION@, hyprlang >= @HYPRLANG_MINIMUM_VERSION@, hyprutils >= @HYPRUTILS_MINIMUM_VERSION@, libdrm, egl, cairo, xkbcommon >= @XKBCOMMON_MINIMUM_VERSION@, libinput >= @LIBINPUT_MINIMUM_VERSION@, wayland-server >= @WAYLAND_SERVER_MINIMUM_VERSION@@PKGCONFIG_XWAYLAND_DEPENDENCIES@\nCflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland -I${prefix}/hyprland/src\n"
  },
  {
    "path": "hyprpm/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.19)\n\nproject(\n    hyprpm\n    DESCRIPTION \"A Hyprland Plugin Manager\"\n)\n\nfile(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS \"src/*.cpp\")\n\nset(CMAKE_CXX_STANDARD 23)\n\npkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)\n\nfind_package(glaze 7...<8 QUIET)\nif (NOT glaze_FOUND)\n    set(GLAZE_VERSION v7.2.0)\n    message(STATUS \"hyprpm: glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent\")\n    include(FetchContent)\n    FetchContent_Declare(\n        glaze\n        GIT_REPOSITORY https://github.com/stephenberry/glaze.git\n        GIT_TAG ${GLAZE_VERSION}\n        GIT_SHALLOW TRUE\n        EXCLUDE_FROM_ALL\n    )\n    FetchContent_MakeAvailable(glaze)\nendif()\n\nadd_executable(hyprpm ${SRCFILES})\n\ntarget_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps glaze::glaze)\n\n# binary\ninstall(TARGETS hyprpm)\n\n# shell completions\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprpm.bash\n        DESTINATION ${CMAKE_INSTALL_DATADIR}/bash-completion/completions\n        RENAME hyprpm)\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprpm.fish\n        DESTINATION ${CMAKE_INSTALL_DATADIR}/fish/vendor_completions.d)\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprpm.zsh\n        DESTINATION ${CMAKE_INSTALL_DATADIR}/zsh/site-functions\n        RENAME _hyprpm)\n"
  },
  {
    "path": "hyprpm/hyprpm.bash",
    "content": "_hyprpm_cmd_0 () {\n    hyprpm list | awk '/Plugin/{print $4}'\n}\n\n_hyprpm_cmd_1 () {\n    hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//'\n}\n\n_hyprpm () {\n    if [[ $(type -t _get_comp_words_by_ref) != function ]]; then\n        echo _get_comp_words_by_ref: function not defined.  Make sure the bash-completions system package is installed\n        return 1\n    fi\n\n    local words cword\n    _get_comp_words_by_ref -n \"$COMP_WORDBREAKS\" words cword\n\n    declare -a literals=(--no-shallow -n ::= disable list --help update add --verbose -v --force -s remove enable --notify -h reload -f)\n    declare -A literal_transitions\n    literal_transitions[0]=\"([0]=7 [3]=3 [4]=4 [8]=7 [9]=7 [6]=4 [7]=4 [11]=7 [5]=7 [10]=7 [12]=2 [13]=3 [15]=7 [16]=4 [17]=7)\"\n    literal_transitions[1]=\"([12]=2 [13]=3 [3]=3 [4]=4 [16]=4 [6]=4 [7]=4)\"\n    literal_transitions[5]=\"([2]=6)\"\n    literal_transitions[6]=\"([1]=7 [14]=7)\"\n    declare -A match_anything_transitions=([1]=1 [4]=5 [3]=4 [2]=4 [0]=1)\n    declare -A subword_transitions\n\n    local state=0\n    local word_index=1\n    while [[ $word_index -lt $cword ]]; do\n        local word=${words[$word_index]}\n\n        if [[ -v \"literal_transitions[$state]\" ]]; then\n            declare -A state_transitions\n            eval \"state_transitions=${literal_transitions[$state]}\"\n\n            local word_matched=0\n            for literal_id in $(seq 0 $((${#literals[@]} - 1))); do\n                if [[ ${literals[$literal_id]} = \"$word\" ]]; then\n                    if [[ -v \"state_transitions[$literal_id]\" ]]; then\n                        state=${state_transitions[$literal_id]}\n                        word_index=$((word_index + 1))\n                        word_matched=1\n                        break\n                    fi\n                fi\n            done\n            if [[ $word_matched -ne 0 ]]; then\n                continue\n            fi\n        fi\n\n        if [[ -v \"match_anything_transitions[$state]\" ]]; then\n            state=${match_anything_transitions[$state]}\n            word_index=$((word_index + 1))\n            continue\n        fi\n\n        return 1\n    done\n\n\n    local -a matches=()\n\n    local prefix=\"${words[$cword]}\"\n    if [[ -v \"literal_transitions[$state]\" ]]; then\n        local state_transitions_initializer=${literal_transitions[$state]}\n        declare -A state_transitions\n        eval \"state_transitions=$state_transitions_initializer\"\n\n        for literal_id in \"${!state_transitions[@]}\"; do\n            local literal=\"${literals[$literal_id]}\"\n            if [[ $literal = \"${prefix}\"* ]]; then\n                matches+=(\"$literal \")\n            fi\n        done\n    fi\n    declare -A commands\n    commands=([3]=0 [2]=1)\n    if [[ -v \"commands[$state]\" ]]; then\n        local command_id=${commands[$state]}\n        local completions=()\n        readarray -t completions < <(_hyprpm_cmd_${command_id} \"$prefix\" | cut -f1)\n        for item in \"${completions[@]}\"; do\n            if [[ $item = \"${prefix}\"* ]]; then\n                matches+=(\"$item\")\n            fi\n        done\n    fi\n\n\n    local shortest_suffix=\"$prefix\"\n    for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do\n        local char=\"${COMP_WORDBREAKS:$i:1}\"\n        local candidate=${prefix##*$char}\n        if [[ ${#candidate} -lt ${#shortest_suffix} ]]; then\n            shortest_suffix=$candidate\n        fi\n    done\n    local superfluous_prefix=\"\"\n    if [[ \"$shortest_suffix\" != \"$prefix\" ]]; then\n        local superfluous_prefix=${prefix%$shortest_suffix}\n    fi\n    COMPREPLY=(\"${matches[@]#$superfluous_prefix}\")\n\n    return 0\n}\n\ncomplete -o nospace -F _hyprpm hyprpm\n"
  },
  {
    "path": "hyprpm/hyprpm.fish",
    "content": "function _hyprpm_1\n    set 1 $argv[1]\n    hyprpm list | awk '/Plugin/{print $4}'\nend\n\nfunction _hyprpm_2\n    set 1 $argv[1]\n    hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//'\nend\n\nfunction _hyprpm\n    set COMP_LINE (commandline --cut-at-cursor)\n\n    set COMP_WORDS\n    echo $COMP_LINE | read --tokenize --array COMP_WORDS\n    if string match --quiet --regex '.*\\s$' $COMP_LINE\n        set COMP_CWORD (math (count $COMP_WORDS) + 1)\n    else\n        set COMP_CWORD (count $COMP_WORDS)\n    end\n\n    set literals \"--no-shallow\" \"-n\" \"::=\" \"disable\" \"list\" \"--help\" \"update\" \"add\" \"--verbose\" \"-v\" \"--force\" \"-s\" \"remove\" \"enable\" \"--notify\" \"-h\" \"reload\" \"-f\"\n\n    set descriptions\n    set descriptions[1] \"Disable shallow cloning of Hyprland sources\"\n    set descriptions[2] \"Send a hyprland notification for important events (e.g. load fail)\"\n    set descriptions[4] \"Unload a plugin\"\n    set descriptions[5] \"List all installed plugins\"\n    set descriptions[6] \"Show help menu\"\n    set descriptions[7] \"Check and update all plugins if needed\"\n    set descriptions[8] \"Install a new plugin repository from git\"\n    set descriptions[9] \"Enable too much logging\"\n    set descriptions[10] \"Enable too much logging\"\n    set descriptions[11] \"Force an operation ignoring checks (e.g. update -f)\"\n    set descriptions[12] \"Disable shallow cloning of Hyprland sources\"\n    set descriptions[13] \"Remove a plugin repository\"\n    set descriptions[14] \"Load a plugin\"\n    set descriptions[15] \"Send a hyprland notification for important events (e.g. load fail)\"\n    set descriptions[16] \"Show help menu\"\n    set descriptions[17] \"Reload all plugins\"\n    set descriptions[18] \"Force an operation ignoring checks (e.g. update -f)\"\n\n    set literal_transitions\n    set literal_transitions[1] \"set inputs 1 4 5 9 10 7 8 12 6 11 13 14 16 17 18; set tos 8 4 5 8 8 5 5 8 8 8 3 4 8 5 8\"\n    set literal_transitions[2] \"set inputs 13 14 4 5 17 7 8; set tos 3 4 4 5 5 5 5\"\n    set literal_transitions[6] \"set inputs 3; set tos 7\"\n    set literal_transitions[7] \"set inputs 2 15; set tos 8 8\"\n\n    set match_anything_transitions_from 2 5 4 3 1\n    set match_anything_transitions_to 2 6 5 5 2\n\n    set state 1\n    set word_index 2\n    while test $word_index -lt $COMP_CWORD\n        set -- word $COMP_WORDS[$word_index]\n\n        if set --query literal_transitions[$state] && test -n $literal_transitions[$state]\n            set --erase inputs\n            set --erase tos\n            eval $literal_transitions[$state]\n\n            if contains -- $word $literals\n                set literal_matched 0\n                for literal_id in (seq 1 (count $literals))\n                    if test $literals[$literal_id] = $word\n                        set index (contains --index -- $literal_id $inputs)\n                        set state $tos[$index]\n                        set word_index (math $word_index + 1)\n                        set literal_matched 1\n                        break\n                    end\n                end\n                if test $literal_matched -ne 0\n                    continue\n                end\n            end\n        end\n\n        if set --query match_anything_transitions_from[$state] && test -n $match_anything_transitions_from[$state]\n            set index (contains --index -- $state $match_anything_transitions_from)\n            set state $match_anything_transitions_to[$index]\n            set word_index (math $word_index + 1)\n            continue\n        end\n\n        return 1\n    end\n\n    if set --query literal_transitions[$state] && test -n $literal_transitions[$state]\n        set --erase inputs\n        set --erase tos\n        eval $literal_transitions[$state]\n        for literal_id in $inputs\n            if test -n $descriptions[$literal_id]\n                printf '%s\\t%s\\n' $literals[$literal_id] $descriptions[$literal_id]\n            else\n                printf '%s\\n' $literals[$literal_id]\n            end\n        end\n    end\n\n    set command_states 4 3\n    set command_ids 1 2\n    if contains $state $command_states\n        set index (contains --index $state $command_states)\n        set function_id $command_ids[$index]\n        set function_name _hyprpm_$function_id\n        set --erase inputs\n        set --erase tos\n        $function_name \"$COMP_WORDS[$COMP_CWORD]\"\n    end\n\n    return 0\nend\n\ncomplete --command hyprpm --no-files --arguments \"(_hyprpm)\"\n"
  },
  {
    "path": "hyprpm/hyprpm.usage",
    "content": "hyprpm [<FLAGS>]... <ARGUMENT>\n\n\n<FLAGS> ::= (--notify | -n)             \"Send a hyprland notification for important events (e.g. load fail)\"\n        |   (--help | -h)               \"Show help menu\"\n        |   (--verbose | -v)            \"Enable too much logging\"\n        |   (--force | -f)              \"Force an operation ignoring checks (e.g. update -f)\"\n        |   (--no-shallow | -s)         \"Disable shallow cloning of Hyprland sources\"\n        ;\n\n<ARGUMENT> ::= (add)                    \"Install a new plugin repository from git\"\n        |   (remove <PLUGIN_REPOS>)     \"Remove a plugin repository\"\n        |   (update)                    \"Check and update all plugins if needed\"\n        |   (list)                      \"List all installed plugins\"\n        |   (enable <PLUGINS>)          \"Load a plugin\"\n        |   (disable <PLUGINS>)         \"Unload a plugin\"\n        |   (reload)                    \"Reload plugins to match the enabled/disabled state. Use -f to force reload.\"\n        ;\n\n<PLUGINS> ::= {{{ hyprpm list | awk '/Plugin/{print $4}' }}};\n<PLUGIN_REPOS> ::= {{{ hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//' }}};\n"
  },
  {
    "path": "hyprpm/hyprpm.zsh",
    "content": "#compdef hyprpm\n\n_hyprpm_cmd_0 () {\n    hyprpm list | awk '/Plugin/{print $4}'\n}\n\n_hyprpm_cmd_1 () {\n    hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//'\n}\n\n_hyprpm () {\n    local -a literals=(\"--no-shallow\" \"-n\" \"::=\" \"disable\" \"list\" \"--help\" \"update\" \"add\" \"--verbose\" \"-v\" \"--force\" \"-s\" \"remove\" \"enable\" \"--notify\" \"-h\" \"reload\" \"-f\")\n\n    local -A descriptions\n    descriptions[1]=\"Disable shallow cloning of Hyprland sources\"\n    descriptions[2]=\"Send a hyprland notification for important events (e.g. load fail)\"\n    descriptions[4]=\"Unload a plugin\"\n    descriptions[5]=\"List all installed plugins\"\n    descriptions[6]=\"Show help menu\"\n    descriptions[7]=\"Check and update all plugins if needed\"\n    descriptions[8]=\"Install a new plugin repository from git\"\n    descriptions[9]=\"Enable too much logging\"\n    descriptions[10]=\"Enable too much logging\"\n    descriptions[11]=\"Force an operation ignoring checks (e.g. update -f)\"\n    descriptions[12]=\"Disable shallow cloning of Hyprland sources\"\n    descriptions[13]=\"Remove a plugin repository\"\n    descriptions[14]=\"Load a plugin\"\n    descriptions[15]=\"Send a hyprland notification for important events (e.g. load fail)\"\n    descriptions[16]=\"Show help menu\"\n    descriptions[17]=\"Reload all plugins\"\n    descriptions[18]=\"Force an operation ignoring checks (e.g. update -f)\"\n\n    local -A literal_transitions\n    literal_transitions[1]=\"([1]=8 [4]=4 [5]=5 [9]=8 [10]=8 [7]=5 [8]=5 [12]=8 [6]=8 [11]=8 [13]=3 [14]=4 [16]=8 [17]=5 [18]=8)\"\n    literal_transitions[2]=\"([13]=3 [14]=4 [4]=4 [5]=5 [17]=5 [7]=5 [8]=5)\"\n    literal_transitions[6]=\"([3]=7)\"\n    literal_transitions[7]=\"([2]=8 [15]=8)\"\n\n    local -A match_anything_transitions\n    match_anything_transitions=([2]=2 [5]=6 [4]=5 [3]=5 [1]=2)\n\n    declare -A subword_transitions\n\n    local state=1\n    local word_index=2\n    while [[ $word_index -lt $CURRENT ]]; do\n        if [[ -v \"literal_transitions[$state]\" ]]; then\n            local -A state_transitions\n            eval \"state_transitions=${literal_transitions[$state]}\"\n\n            local word=${words[$word_index]}\n            local word_matched=0\n            for ((literal_id = 1; literal_id <= $#literals; literal_id++)); do\n                if [[ ${literals[$literal_id]} = \"$word\" ]]; then\n                    if [[ -v \"state_transitions[$literal_id]\" ]]; then\n                        state=${state_transitions[$literal_id]}\n                        word_index=$((word_index + 1))\n                        word_matched=1\n                        break\n                    fi\n                fi\n            done\n            if [[ $word_matched -ne 0 ]]; then\n                continue\n            fi\n        fi\n\n        if [[ -v \"match_anything_transitions[$state]\" ]]; then\n            state=${match_anything_transitions[$state]}\n            word_index=$((word_index + 1))\n            continue\n        fi\n\n        return 1\n    done\n\n    completions_no_description_trailing_space=()\n    completions_no_description_no_trailing_space=()\n    completions_trailing_space=()\n    suffixes_trailing_space=()\n    descriptions_trailing_space=()\n    completions_no_trailing_space=()\n    suffixes_no_trailing_space=()\n    descriptions_no_trailing_space=()\n\n    if [[ -v \"literal_transitions[$state]\" ]]; then\n        local -A state_transitions\n        eval \"state_transitions=${literal_transitions[$state]}\"\n\n        for literal_id in ${(k)state_transitions}; do\n            if [[ -v \"descriptions[$literal_id]\" ]]; then\n                completions_trailing_space+=(\"${literals[$literal_id]}\")\n                suffixes_trailing_space+=(\"${literals[$literal_id]}\")\n                descriptions_trailing_space+=(\"${descriptions[$literal_id]}\")\n            else\n                completions_no_description_trailing_space+=(\"${literals[$literal_id]}\")\n            fi\n        done\n    fi\n    local -A commands=([4]=0 [3]=1)\n\n    if [[ -v \"commands[$state]\" ]]; then\n        local command_id=${commands[$state]}\n        local output=$(_hyprpm_cmd_${command_id} \"${words[$CURRENT]}\")\n        local -a command_completions=(\"${(@f)output}\")\n        for line in ${command_completions[@]}; do\n            local parts=(${(@s:\t:)line})\n            if [[ -v \"parts[2]\" ]]; then\n                completions_trailing_space+=(\"${parts[1]}\")\n                suffixes_trailing_space+=(\"${parts[1]}\")\n                descriptions_trailing_space+=(\"${parts[2]}\")\n            else\n                completions_no_description_trailing_space+=(\"${parts[1]}\")\n            fi\n        done\n    fi\n\n    local maxlen=0\n    for suffix in ${suffixes_trailing_space[@]}; do\n        if [[ ${#suffix} -gt $maxlen ]]; then\n            maxlen=${#suffix}\n        fi\n    done\n    for suffix in ${suffixes_no_trailing_space[@]}; do\n        if [[ ${#suffix} -gt $maxlen ]]; then\n            maxlen=${#suffix}\n        fi\n    done\n\n    for ((i = 1; i <= $#suffixes_trailing_space; i++)); do\n        if [[ -z ${descriptions_trailing_space[$i]} ]]; then\n            descriptions_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_trailing_space[$i]}}\"\n        else\n            descriptions_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_trailing_space[$i]}} -- ${descriptions_trailing_space[$i]}\"\n        fi\n    done\n\n    for ((i = 1; i <= $#suffixes_no_trailing_space; i++)); do\n        if [[ -z ${descriptions_no_trailing_space[$i]} ]]; then\n            descriptions_no_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_no_trailing_space[$i]}}\"\n        else\n            descriptions_no_trailing_space[$i]=\"${(r($maxlen)( ))${suffixes_no_trailing_space[$i]}} -- ${descriptions_no_trailing_space[$i]}\"\n        fi\n    done\n\n    compadd -Q -a completions_no_description_trailing_space\n    compadd -Q -S ' ' -a completions_no_description_no_trailing_space\n    compadd -l -Q -a -d descriptions_trailing_space completions_trailing_space\n    compadd -l -Q -S '' -a -d descriptions_no_trailing_space completions_no_trailing_space\n    return 0\n}\n\nif [[ $ZSH_EVAL_CONTEXT =~ :file$ ]]; then\n    compdef _hyprpm hyprpm\nelse\n    _hyprpm\nfi\n"
  },
  {
    "path": "hyprpm/src/core/DataState.cpp",
    "content": "#include \"DataState.hpp\"\n#include <sys/stat.h>\n#include <toml++/toml.hpp>\n#include <print>\n#include <sstream>\n#include <fstream>\n#include \"PluginManager.hpp\"\n#include \"../helpers/Die.hpp\"\n#include \"../helpers/Sys.hpp\"\n#include \"../helpers/StringUtils.hpp\"\n\nstatic std::string getTempRoot() {\n    static auto ENV = getenv(\"XDG_RUNTIME_DIR\");\n    if (!ENV) {\n        std::cerr << \"\\nERROR: XDG_RUNTIME_DIR not set!\\n\";\n        exit(1);\n    }\n\n    const auto STR = ENV + std::string{\"/hyprpm/\"};\n\n    if (!std::filesystem::exists(STR))\n        mkdir(STR.c_str(), S_IRWXU);\n\n    return STR;\n}\n\n// write the state to a file\nstatic bool writeState(const std::string& str, const std::string& to) {\n    // create temp file in a safe temp root\n    std::ofstream of(getTempRoot() + \".temp-state\", std::ios::trunc);\n    if (!of.good())\n        return false;\n\n    of << str;\n    of.close();\n\n    return NSys::root::install(getTempRoot() + \".temp-state\", to, \"644\");\n}\n\nstd::filesystem::path DataState::getDataStatePath() {\n    return std::filesystem::path(\"/var/cache/hyprpm/\" + g_pPluginManager->m_szUsername);\n}\n\nstd::string DataState::getHeadersPath() {\n    return getDataStatePath() / \"headersRoot\";\n}\n\nstd::vector<std::filesystem::path> DataState::getPluginStates() {\n    ensureStateStoreExists();\n\n    std::vector<std::filesystem::path> states;\n    for (const auto& entry : std::filesystem::directory_iterator(getDataStatePath())) {\n        if (!entry.is_directory() || entry.path().stem() == \"headersRoot\")\n            continue;\n\n        const auto stateFile = entry.path() / \"state.toml\";\n        if (!std::filesystem::exists(stateFile))\n            continue;\n\n        states.emplace_back(stateFile);\n    }\n    return states;\n}\n\nvoid DataState::ensureStateStoreExists() {\n    std::error_code ec;\n    if (!std::filesystem::exists(getHeadersPath(), ec) || ec) {\n        std::println(\"{}\", infoString(\"The hyprpm state store doesn't exist. Creating now...\"));\n        if (!std::filesystem::exists(\"/var/cache/hyprpm/\", ec) || ec) {\n            if (!NSys::root::createDirectory(\"/var/cache/hyprpm\", \"755\"))\n                Debug::die(\"ensureStateStoreExists: Failed to run a superuser cmd\");\n        }\n        if (!std::filesystem::exists(getDataStatePath(), ec) || ec) {\n            if (!NSys::root::createDirectory(getDataStatePath().string(), \"755\"))\n                Debug::die(\"ensureStateStoreExists: Failed to run a superuser cmd\");\n        }\n        if (!NSys::root::createDirectory(getHeadersPath(), \"755\"))\n            Debug::die(\"ensureStateStoreExists: Failed to run a superuser cmd\");\n    }\n}\n\nvoid DataState::addNewPluginRepo(const SPluginRepository& repo) {\n    ensureStateStoreExists();\n\n    const auto      PATH = getDataStatePath() / repo.name;\n\n    std::error_code ec;\n    if (!std::filesystem::exists(PATH, ec) || ec) {\n        if (!NSys::root::createDirectory(PATH.string(), \"755\"))\n            Debug::die(\"addNewPluginRepo: failed to create cache dir\");\n    }\n    // clang-format off\n    auto DATA = toml::table{\n        {\"repository\", toml::table{\n            {\"name\", repo.name},\n            {\"author\", repo.author},\n            {\"hash\", repo.hash},\n            {\"url\", repo.url},\n            {\"rev\", repo.rev}\n        }}\n    };\n    for (auto const& p : repo.plugins) {\n        const auto filename = p.name + \".so\";\n\n        // copy .so to the good place and chmod 755\n        if (std::filesystem::exists(p.filename)) {\n            if (!NSys::root::install(p.filename, (PATH / filename).string(), \"0755\"))\n                Debug::die(\"addNewPluginRepo: failed to install so file\");\n        }\n\n        DATA.emplace(p.name, toml::table{\n            {\"filename\", filename},\n            {\"enabled\", p.enabled},\n            {\"failed\", p.failed}\n        });\n    }\n    // clang-format on\n\n    std::stringstream ss;\n    ss << DATA;\n\n    if (!writeState(ss.str(), (PATH / \"state.toml\").string()))\n        Debug::die(\"{}\", failureString(\"Failed to write plugin state\"));\n}\n\nbool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) {\n    ensureStateStoreExists();\n\n    for (const auto& stateFile : getPluginStates()) {\n        const auto STATE  = toml::parse_file(stateFile.c_str());\n        const auto NAME   = STATE[\"repository\"][\"name\"].value_or(\"\");\n        const auto AUTHOR = STATE[\"repository\"][\"author\"].value_or(\"\");\n        const auto URL    = STATE[\"repository\"][\"url\"].value_or(\"\");\n\n        if (identifier.matches(URL, NAME, AUTHOR))\n            return true;\n    }\n\n    return false;\n}\n\nvoid DataState::removePluginRepo(const SPluginRepoIdentifier identifier) {\n    ensureStateStoreExists();\n\n    for (const auto& stateFile : getPluginStates()) {\n        const auto STATE  = toml::parse_file(stateFile.c_str());\n        const auto NAME   = STATE[\"repository\"][\"name\"].value_or(\"\");\n        const auto AUTHOR = STATE[\"repository\"][\"author\"].value_or(\"\");\n        const auto URL    = STATE[\"repository\"][\"url\"].value_or(\"\");\n\n        if (identifier.matches(URL, NAME, AUTHOR)) {\n            // unload the plugins!!\n            for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) {\n                if (!file.path().string().ends_with(\".so\"))\n                    continue;\n\n                g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false);\n            }\n\n            const auto PATH = stateFile.parent_path().string();\n\n            if (!PATH.starts_with(\"/var/cache/hyprpm\") || PATH.contains('\\''))\n                return; // WTF?\n\n            // scary!\n            if (!NSys::root::removeRecursive(PATH))\n                Debug::die(\"removePluginRepo: failed to remove dir\");\n            return;\n        }\n    }\n}\n\nvoid DataState::updateGlobalState(const SGlobalState& state) {\n    ensureStateStoreExists();\n\n    const auto      PATH = getDataStatePath();\n\n    std::error_code ec;\n    if (!std::filesystem::exists(PATH, ec) || ec) {\n        if (!NSys::root::createDirectory(PATH.string(), \"755\"))\n            Debug::die(\"updateGlobalState: failed to create dir\");\n    }\n    // clang-format off\n    auto DATA = toml::table{\n        {\"state\", toml::table{\n            {\"hash\", state.headersAbiCompiled},\n            {\"dont_warn_install\", state.dontWarnInstall}\n        }}\n    };\n    // clang-format on\n\n    std::stringstream ss;\n    ss << DATA;\n\n    if (!writeState(ss.str(), (PATH / \"state.toml\").string()))\n        Debug::die(\"{}\", failureString(\"Failed to write plugin state\"));\n}\n\nSGlobalState DataState::getGlobalState() {\n    ensureStateStoreExists();\n\n    const auto      stateFile = getDataStatePath() / \"state.toml\";\n\n    std::error_code ec;\n    if (!std::filesystem::exists(stateFile, ec) || ec)\n        return SGlobalState{};\n\n    auto         DATA = toml::parse_file(stateFile.c_str());\n\n    SGlobalState state;\n    state.headersAbiCompiled = DATA[\"state\"][\"hash\"].value_or(\"\");\n    state.dontWarnInstall    = DATA[\"state\"][\"dont_warn_install\"].value_or(false);\n\n    return state;\n}\n\nstd::vector<SPluginRepository> DataState::getAllRepositories() {\n    ensureStateStoreExists();\n\n    std::vector<SPluginRepository> repos;\n    for (const auto& stateFile : getPluginStates()) {\n        const auto        STATE = toml::parse_file(stateFile.c_str());\n\n        const auto        NAME   = STATE[\"repository\"][\"name\"].value_or(\"\");\n        const auto        AUTHOR = STATE[\"repository\"][\"author\"].value_or(\"\");\n        const auto        URL    = STATE[\"repository\"][\"url\"].value_or(\"\");\n        const auto        REV    = STATE[\"repository\"][\"rev\"].value_or(\"\");\n        const auto        HASH   = STATE[\"repository\"][\"hash\"].value_or(\"\");\n\n        SPluginRepository repo;\n        repo.hash   = HASH;\n        repo.name   = NAME;\n        repo.author = AUTHOR;\n        repo.url    = URL;\n        repo.rev    = REV;\n\n        for (const auto& [key, val] : STATE) {\n            if (key == \"repository\")\n                continue;\n\n            const auto ENABLED  = STATE[key][\"enabled\"].value_or(false);\n            const auto FAILED   = STATE[key][\"failed\"].value_or(false);\n            const auto FILENAME = STATE[key][\"filename\"].value_or(\"\");\n\n            repo.plugins.push_back(SPlugin{std::string{key.str()}, FILENAME, ENABLED, FAILED});\n        }\n\n        repos.push_back(repo);\n    }\n\n    return repos;\n}\n\nbool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) {\n    ensureStateStoreExists();\n\n    for (const auto& stateFile : getPluginStates()) {\n        const auto STATE = toml::parse_file(stateFile.c_str());\n        for (const auto& [key, val] : STATE) {\n            if (key == \"repository\")\n                continue;\n\n            switch (identifier.type) {\n                case IDENTIFIER_NAME:\n                    if (key.str() != identifier.name)\n                        continue;\n                    break;\n                case IDENTIFIER_AUTHOR_NAME:\n                    if (STATE[\"repository\"][\"author\"] != identifier.author || key.str() != identifier.name)\n                        continue;\n                    break;\n                default: return false;\n            }\n\n            const auto FAILED = STATE[key][\"failed\"].value_or(false);\n\n            if (FAILED)\n                return false;\n\n            auto modifiedState = STATE;\n            (*modifiedState[key].as_table()).insert_or_assign(\"enabled\", enabled);\n\n            std::stringstream ss;\n            ss << modifiedState;\n\n            if (!writeState(ss.str(), stateFile.string()))\n                Debug::die(\"{}\", failureString(\"Failed to write plugin state\"));\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\nvoid DataState::purgeAllCache() {\n    std::error_code ec;\n    if (!std::filesystem::exists(getDataStatePath()) && !ec) {\n        std::println(\"{}\", infoString(\"Nothing to do\"));\n        return;\n    }\n\n    const auto PATH = getDataStatePath().string();\n    if (PATH.contains('\\''))\n        return;\n    // scary!\n    if (!NSys::root::removeRecursive(PATH))\n        Debug::die(\"Failed to run a superuser cmd\");\n}\n"
  },
  {
    "path": "hyprpm/src/core/DataState.hpp",
    "content": "#pragma once\n#include <filesystem>\n#include <string>\n#include <vector>\n#include \"Plugin.hpp\"\n\nstruct SGlobalState {\n    std::string headersAbiCompiled = \"\";\n    bool        dontWarnInstall    = false;\n};\n\nnamespace DataState {\n    std::filesystem::path              getDataStatePath();\n    std::string                        getHeadersPath();\n    std::vector<std::filesystem::path> getPluginStates();\n    void                               ensureStateStoreExists();\n    void                               addNewPluginRepo(const SPluginRepository& repo);\n    void                               removePluginRepo(const SPluginRepoIdentifier identifier);\n    bool                               pluginRepoExists(const SPluginRepoIdentifier identifier);\n    void                               updateGlobalState(const SGlobalState& state);\n    void                               purgeAllCache();\n    SGlobalState                       getGlobalState();\n    bool                               setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled);\n    std::vector<SPluginRepository>     getAllRepositories();\n};\n"
  },
  {
    "path": "hyprpm/src/core/HyprlandSocket.cpp",
    "content": "#include \"HyprlandSocket.hpp\"\n#include <pwd.h>\n#include <sys/socket.h>\n#include \"../helpers/StringUtils.hpp\"\n#include <print>\n#include <sys/un.h>\n#include <unistd.h>\n#include <cstring>\n#include <hyprutils/memory/Casts.hpp>\n\nusing namespace Hyprutils::Memory;\n\nstatic int getUID() {\n    const auto UID   = getuid();\n    const auto PWUID = getpwuid(UID);\n    return PWUID ? PWUID->pw_uid : UID;\n}\n\nstatic std::string getRuntimeDir() {\n    const auto XDG = getenv(\"XDG_RUNTIME_DIR\");\n\n    if (!XDG) {\n        const std::string USERID = std::to_string(getUID());\n        return \"/run/user/\" + USERID + \"/hypr\";\n    }\n\n    return std::string{XDG} + \"/hypr\";\n}\n\nstd::string NHyprlandSocket::send(const std::string& cmd) {\n    const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);\n\n    if (SERVERSOCKET < 0) {\n        std::println(\"{}\", failureString(\"Couldn't open a socket (1)\"));\n        return \"\";\n    }\n\n    const auto HIS = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n    if (!HIS) {\n        std::println(\"{}\", failureString(\"HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)\"));\n        return \"\";\n    }\n\n    sockaddr_un serverAddress = {0};\n    serverAddress.sun_family  = AF_UNIX;\n\n    std::string socketPath = getRuntimeDir() + \"/\" + HIS + \"/.socket.sock\";\n\n    strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);\n\n    if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {\n        std::println(\"{}\", failureString(\"Couldn't connect to \" + socketPath + \". (4)\"));\n        return \"\";\n    }\n\n    auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());\n\n    if (sizeWritten < 0) {\n        std::println(\"{}\", failureString(\"Couldn't write (5)\"));\n        return \"\";\n    }\n\n    std::string      reply               = \"\";\n    constexpr size_t BUFFER_SIZE         = 8192;\n    char             buffer[BUFFER_SIZE] = {0};\n\n    sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);\n\n    if (sizeWritten < 0) {\n        std::println(\"{}\", failureString(\"Couldn't read (6)\"));\n        return \"\";\n    }\n\n    reply += std::string(buffer, sizeWritten);\n\n    while (sizeWritten == BUFFER_SIZE) {\n        sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);\n        if (sizeWritten < 0) {\n            std::println(\"{}\", failureString(\"Couldn't read (7)\"));\n            return \"\";\n        }\n        reply += std::string(buffer, sizeWritten);\n    }\n\n    close(SERVERSOCKET);\n\n    return reply;\n}\n"
  },
  {
    "path": "hyprpm/src/core/HyprlandSocket.hpp",
    "content": "#pragma once\n\n#include <string>\n\nnamespace NHyprlandSocket {\n    std::string send(const std::string& cmd);\n};"
  },
  {
    "path": "hyprpm/src/core/Manifest.cpp",
    "content": "#include \"Manifest.hpp\"\n#include <toml++/toml.hpp>\n#include <algorithm>\n\n// Alphanumerics and -_ allowed for plugin names. No magic names.\n// [A-Za-z0-9\\-_]*\nstatic bool validManifestName(const std::string_view& n) {\n    return std::ranges::all_of(n, [](const char& c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || c == '=' || (c >= '0' && c <= '9'); });\n}\n\nCManifest::CManifest(const eManifestType type, const std::string& path) {\n    auto manifest = toml::parse_file(path);\n\n    if (type == MANIFEST_HYPRLOAD) {\n        for (auto const& [key, val] : manifest) {\n            if (key.str().ends_with(\".build\"))\n                continue;\n\n            CManifest::SManifestPlugin plugin;\n\n            if (!validManifestName(key.str())) {\n                m_good = false;\n                return;\n            }\n\n            plugin.name = key;\n            m_plugins.push_back(plugin);\n        }\n\n        for (auto& plugin : m_plugins) {\n            plugin.description = manifest[plugin.name][\"description\"].value_or(\"?\");\n            plugin.version     = manifest[plugin.name][\"version\"].value_or(\"?\");\n            plugin.output      = manifest[plugin.name][\"build\"][\"output\"].value_or(\"?\");\n            auto authors       = manifest[plugin.name][\"authors\"].as_array();\n            if (authors) {\n                for (auto&& a : *authors) {\n                    plugin.authors.push_back(a.as_string()->value_or(\"?\"));\n                }\n            } else {\n                auto author = manifest[plugin.name][\"author\"].value_or(\"\");\n                if (!std::string{author}.empty())\n                    plugin.authors.push_back(author);\n            }\n            auto buildSteps = manifest[plugin.name][\"build\"][\"steps\"].as_array();\n            if (buildSteps) {\n                for (auto&& s : *buildSteps) {\n                    plugin.buildSteps.push_back(s.as_string()->value_or(\"?\"));\n                }\n            }\n\n            if (plugin.output.empty() || plugin.buildSteps.empty()) {\n                m_good = false;\n                return;\n            }\n        }\n    } else if (type == MANIFEST_HYPRPM) {\n        m_repository.name = manifest[\"repository\"][\"name\"].value_or(\"\");\n        auto authors      = manifest[\"repository\"][\"authors\"].as_array();\n        if (authors) {\n            for (auto&& a : *authors) {\n                m_repository.authors.push_back(a.as_string()->value_or(\"?\"));\n            }\n        } else {\n            auto author = manifest[\"repository\"][\"author\"].value_or(\"\");\n            if (!std::string{author}.empty())\n                m_repository.authors.push_back(author);\n        }\n\n        auto pins = manifest[\"repository\"][\"commit_pins\"].as_array();\n        if (pins) {\n            for (auto&& pin : *pins) {\n                auto pinArr = pin.as_array();\n                if (pinArr && pinArr->get(1))\n                    m_repository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));\n            }\n        }\n\n        for (auto const& [key, val] : manifest) {\n            if (key.str() == \"repository\")\n                continue;\n\n            CManifest::SManifestPlugin plugin;\n\n            if (!validManifestName(key.str())) {\n                m_good = false;\n                return;\n            }\n\n            plugin.name = key;\n            m_plugins.push_back(plugin);\n        }\n\n        for (auto& plugin : m_plugins) {\n            plugin.description = manifest[plugin.name][\"description\"].value_or(\"?\");\n            plugin.output      = manifest[plugin.name][\"output\"].value_or(\"?\");\n            plugin.since       = manifest[plugin.name][\"since_hyprland\"].value_or(0);\n            auto authors       = manifest[plugin.name][\"authors\"].as_array();\n            if (authors) {\n                for (auto&& a : *authors) {\n                    plugin.authors.push_back(a.as_string()->value_or(\"?\"));\n                }\n            } else {\n                auto author = manifest[plugin.name][\"author\"].value_or(\"\");\n                if (!std::string{author}.empty())\n                    plugin.authors.push_back(author);\n            }\n            auto buildSteps = manifest[plugin.name][\"build\"].as_array();\n            if (buildSteps) {\n                for (auto&& s : *buildSteps) {\n                    plugin.buildSteps.push_back(s.as_string()->value_or(\"?\"));\n                }\n            }\n\n            if (plugin.output.empty() || plugin.buildSteps.empty()) {\n                m_good = false;\n                return;\n            }\n        }\n    } else {\n        // ???\n        m_good = false;\n    }\n}"
  },
  {
    "path": "hyprpm/src/core/Manifest.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n\nenum eManifestType {\n    MANIFEST_HYPRLOAD,\n    MANIFEST_HYPRPM\n};\n\nclass CManifest {\n  public:\n    CManifest(const eManifestType type, const std::string& path);\n\n    struct SManifestPlugin {\n        std::string              name;\n        std::string              description;\n        std::string              version;\n        std::vector<std::string> authors;\n        std::vector<std::string> buildSteps;\n        std::string              output;\n        int                      since  = 0;\n        bool                     failed = false;\n    };\n\n    struct {\n        std::string                                      name;\n        std::vector<std::string>                         authors;\n        std::vector<std::pair<std::string, std::string>> commitPins;\n    } m_repository;\n\n    std::vector<SManifestPlugin> m_plugins;\n    bool                         m_good = true;\n};"
  },
  {
    "path": "hyprpm/src/core/Plugin.cpp",
    "content": "#include \"Plugin.hpp\"\n\nSPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) {\n    return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url};\n}\n\nSPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) {\n    return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name};\n}\n\nSPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) {\n    return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};\n}\n\nSPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) {\n    if (string.find(':') != std::string::npos) {\n        return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string};\n    } else {\n        auto slashPos = string.find('/');\n        if (slashPos != std::string::npos) {\n            std::string author = string.substr(0, slashPos);\n            std::string name   = string.substr(slashPos + 1, string.size() - slashPos - 1);\n            return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};\n        } else {\n            return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string};\n        }\n    }\n}\n\nstd::string SPluginRepoIdentifier::toString() const {\n    switch (type) {\n        case IDENTIFIER_NAME: return name;\n        case IDENTIFIER_AUTHOR_NAME: return author + '/' + name;\n        case IDENTIFIER_URL: return url;\n    }\n\n    return \"\";\n}\n\nbool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const {\n    switch (type) {\n        case IDENTIFIER_URL: return this->url == url;\n        case IDENTIFIER_NAME: return this->name == name;\n        case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "hyprpm/src/core/Plugin.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n\nstruct SPlugin {\n    std::string name;\n    std::string filename;\n    bool        enabled = false;\n    bool        failed  = false;\n};\n\nstruct SPluginRepository {\n    std::string          url;\n    std::string          rev;\n    std::string          name;\n    std::string          author;\n    std::vector<SPlugin> plugins;\n    std::string          hash;\n};\n\nenum ePluginRepoIdentifierType {\n    IDENTIFIER_URL,\n    IDENTIFIER_NAME,\n    IDENTIFIER_AUTHOR_NAME\n};\n\nstruct SPluginRepoIdentifier {\n    ePluginRepoIdentifierType    type;\n    std::string                  url    = \"\";\n    std::string                  name   = \"\";\n    std::string                  author = \"\";\n\n    static SPluginRepoIdentifier fromString(const std::string& string);\n    static SPluginRepoIdentifier fromUrl(const std::string& Url);\n    static SPluginRepoIdentifier fromName(const std::string& name);\n    static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name);\n    std::string                  toString() const;\n    bool                         matches(const std::string& url, const std::string& name, const std::string& author) const;\n};\n"
  },
  {
    "path": "hyprpm/src/core/PluginManager.cpp",
    "content": "#include \"PluginManager.hpp\"\n#include \"../helpers/Colors.hpp\"\n#include \"../helpers/StringUtils.hpp\"\n#include \"../progress/CProgressBar.hpp\"\n#include \"Manifest.hpp\"\n#include \"DataState.hpp\"\n#include \"HyprlandSocket.hpp\"\n#include \"../helpers/Sys.hpp\"\n#include \"../helpers/Die.hpp\"\n\n#include <cstdio>\n#include <iostream>\n#include <filesystem>\n#include <string>\n#include <print>\n#include <fstream>\n#include <algorithm>\n#include <format>\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <pwd.h>\n#include <unistd.h>\n\n#include <toml++/toml.hpp>\n#include <glaze/glaze.hpp>\n\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/Casts.hpp>\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\nstatic std::string execAndGet(std::string cmd) {\n    cmd += \" 2>&1\";\n\n    CProcess proc(\"/bin/sh\", {\"-c\", cmd});\n\n    if (!proc.runSync())\n        return \"error\";\n\n    return proc.stdOut();\n}\n\nstatic std::string getTempRoot() {\n    static auto ENV = getenv(\"XDG_RUNTIME_DIR\");\n    if (!ENV) {\n        std::cerr << \"\\nERROR: XDG_RUNTIME_DIR not set!\\n\";\n        exit(1);\n    }\n\n    const auto STR = ENV + std::string{\"/hyprpm/\"};\n\n    return STR;\n}\n\nCPluginManager::CPluginManager() {\n    if (NSys::isSuperuser())\n        Debug::die(\"Don't run hyprpm as a superuser.\");\n\n    m_szUsername = getpwuid(NSys::getUID())->pw_name;\n}\n\nSHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {\n    static bool             onceRunning   = false;\n    static bool             onceInstalled = false;\n    static SHyprlandVersion verRunning;\n    static SHyprlandVersion verInstalled;\n\n    if (onceRunning && running)\n        return verRunning;\n\n    if (onceInstalled && !running)\n        return verInstalled;\n\n    if (running)\n        onceRunning = true;\n    else\n        onceInstalled = true;\n\n    const auto HLVERCALL = running ? NHyprlandSocket::send(\"j/version\") : execAndGet(\"Hyprland --version-json\");\n\n    auto       jsonQuery = glz::read_json<glz::generic>(HLVERCALL);\n\n    if (!jsonQuery) {\n        std::println(\"{}\", failureString(\"failed to get the current hyprland version. Are you running hyprland?\"));\n        return SHyprlandVersion{};\n    }\n\n    auto   hlbranch  = (*jsonQuery)[\"branch\"].get_string();\n    auto   hlcommit  = (*jsonQuery)[\"commit\"].get_string();\n    auto   abiHash   = (*jsonQuery)[\"abiHash\"].get_string();\n    auto   hldate    = (*jsonQuery)[\"commit_date\"].get_string();\n    auto   hlcommits = (*jsonQuery)[\"commits\"].get_string();\n\n    auto   flags = (*jsonQuery)[\"flags\"].get_array();\n    bool   isNix = std::ranges::any_of(flags, [](const auto& f) { return f.is_string() && f.get_string() == std::string_view{\"nix\"}; });\n\n    size_t commits = 0;\n    try {\n        commits = std::stoull(hlcommits);\n    } catch (...) { ; }\n\n    if (m_bVerbose)\n        std::println(\"{}\", verboseString(\"parsed commit {} at branch {} on {}, commits {}, nix: {}\", hlcommit, hlbranch, hldate, commits, isNix));\n\n    auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix};\n\n    if (running)\n        verRunning = ver;\n    else\n        verInstalled = ver;\n\n    return ver;\n}\n\nbool CPluginManager::createSafeDirectory(const std::string& path) {\n    if (path.empty() || !path.starts_with(getTempRoot()))\n        return false;\n\n    if (std::filesystem::exists(path))\n        std::filesystem::remove_all(path);\n\n    if (std::filesystem::exists(path))\n        return false;\n\n    if (mkdir(path.c_str(), S_IRWXU) < 0)\n        return false;\n\n    return true;\n}\n\nbool CPluginManager::validArg(const std::string& s) {\n    return !s.contains(\"'\") && !s.ends_with(\"\\\\\") && !s.starts_with(\"\\\\\");\n}\n\nbool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) {\n    const auto HLVER = getHyprlandVersion();\n\n    if (!validArg(url) || !validArg(rev)) {\n        std::println(stderr, \"\\n{}\", failureString(\"url or rev invalid\"));\n        return false;\n    }\n\n    if (!hasDeps()) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc\"));\n        return false;\n    }\n\n    if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not clone the plugin repository. Repository already installed.\"));\n        return false;\n    }\n\n    auto GLOBALSTATE = DataState::getGlobalState();\n    if (!GLOBALSTATE.dontWarnInstall) {\n        std::println(\"{}!{} Disclaimer: {}\", Colors::YELLOW, Colors::RED, Colors::RESET);\n        std::println(\"plugins, especially not official, have no guarantee of stability, availability or security.\\n\"\n                     \"Run them at your own risk.\\n\"\n                     \"This message will not appear again.\");\n        GLOBALSTATE.dontWarnInstall = true;\n        DataState::updateGlobalState(GLOBALSTATE);\n    }\n\n    if (GLOBALSTATE.headersAbiCompiled.empty()) {\n        std::println(\"\\n{}\", failureString(\"Cannot find headers in the global state. Try running hyprpm update first.\"));\n        return false;\n    }\n\n    std::cout << Colors::GREEN << \"✔\" << Colors::RESET << Colors::RED << \" adding a new plugin repository \" << Colors::RESET << \"from \" << url << \"\\n  \" << Colors::RED\n              << \"MAKE SURE\" << Colors::RESET << \" that you trust the authors. \" << Colors::RED << \"DO NOT\" << Colors::RESET\n              << \" install random plugins without verifying the code and author.\\n  \"\n              << \"Are you sure? [Y/n] \";\n    std::fflush(stdout);\n    std::string input;\n    std::getline(std::cin, input);\n\n    if (input.size() > 0 && input[0] != 'Y' && input[0] != 'y') {\n        std::println(stderr, \"Aborting.\");\n        return false;\n    }\n\n    CProgressBar progress;\n    progress.m_iMaxSteps        = 5;\n    progress.m_iSteps           = 0;\n    progress.m_szCurrentMessage = \"Cloning the plugin repository\";\n\n    progress.print();\n\n    if (!std::filesystem::exists(getTempRoot())) {\n        std::filesystem::create_directory(getTempRoot());\n        std::filesystem::permissions(getTempRoot(), std::filesystem::perms::owner_all, std::filesystem::perm_options::replace);\n    } else if (!std::filesystem::is_directory(getTempRoot())) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not prepare working dir for hyprpm\"));\n        return false;\n    }\n\n    const std::string USERNAME = getpwuid(getuid())->pw_name;\n\n    m_szWorkingPluginDirectory = getTempRoot() + USERNAME;\n\n    if (!createSafeDirectory(m_szWorkingPluginDirectory)) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not prepare working dir for repo\"));\n        return false;\n    }\n\n    progress.printMessageAbove(infoString(\"Cloning {}\", url));\n\n    std::string ret = execAndGet(std::format(\"cd {} && git clone --recursive '{}' {}\", getTempRoot(), url, USERNAME));\n\n    if (!std::filesystem::exists(m_szWorkingPluginDirectory + \"/.git\")) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not clone the plugin repository. shell returned:\\n{}\", ret));\n        return false;\n    }\n\n    if (!rev.empty()) {\n        std::string ret = execAndGet(\"git -C \" + m_szWorkingPluginDirectory + \" reset --hard --recurse-submodules \" + rev);\n        if (ret.compare(0, 6, \"fatal:\") == 0) {\n            std::println(stderr, \"\\n{}\", failureString(\"Could not check out revision {}. shell returned:\\n{}\", rev, ret));\n            return false;\n        }\n        ret = execAndGet(\"git -C \" + m_szWorkingPluginDirectory + \" submodule update --init\");\n        if (m_bVerbose)\n            std::println(\"{}\", verboseString(\"git submodule update --init returned: {}\", ret));\n    }\n\n    progress.m_iSteps = 1;\n    progress.printMessageAbove(successString(\"cloned\"));\n    progress.m_szCurrentMessage = \"Reading the manifest\";\n    progress.print();\n\n    std::unique_ptr<CManifest> pManifest;\n\n    if (std::filesystem::exists(m_szWorkingPluginDirectory + \"/hyprpm.toml\")) {\n        progress.printMessageAbove(successString(\"found hyprpm manifest\"));\n        pManifest = std::make_unique<CManifest>(MANIFEST_HYPRPM, m_szWorkingPluginDirectory + \"/hyprpm.toml\");\n    } else if (std::filesystem::exists(m_szWorkingPluginDirectory + \"/hyprload.toml\")) {\n        progress.printMessageAbove(successString(\"found hyprload manifest\"));\n        pManifest = std::make_unique<CManifest>(MANIFEST_HYPRLOAD, m_szWorkingPluginDirectory + \"/hyprload.toml\");\n    }\n\n    if (!pManifest) {\n        std::println(stderr, \"\\n{}\", failureString(\"The provided plugin repository does not have a valid manifest\"));\n        return false;\n    }\n\n    if (!pManifest->m_good) {\n        std::println(stderr, \"\\n{}\", failureString(\"The provided plugin repository has a corrupted manifest\"));\n        return false;\n    }\n\n    progress.m_iSteps = 2;\n    progress.printMessageAbove(successString(\"parsed manifest, found \" + std::to_string(pManifest->m_plugins.size()) + \" plugins:\"));\n    for (auto const& pl : pManifest->m_plugins) {\n        std::string message = \"→ \" + pl.name + \" by \";\n        for (auto const& a : pl.authors) {\n            message += a + \", \";\n        }\n        if (pl.authors.size() > 0) {\n            message.pop_back();\n            message.pop_back();\n        }\n        message += \" version \" + pl.version;\n        progress.printMessageAbove(message);\n    }\n\n    if (rev.empty() && !pManifest->m_repository.commitPins.empty()) {\n        // check commit pins\n\n        progress.printMessageAbove(infoString(\"Manifest has {} pins, checking\", pManifest->m_repository.commitPins.size()));\n\n        for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) {\n            if (hl != HLVER.hash)\n                continue;\n\n            progress.printMessageAbove(successString(\"commit pin {} matched hl, resetting\", plugin));\n\n            execAndGet(\"cd \" + m_szWorkingPluginDirectory + \" && git reset --hard --recurse-submodules \" + plugin);\n\n            ret = execAndGet(\"git -C \" + m_szWorkingPluginDirectory + \" submodule update --init\");\n            if (m_bVerbose)\n                std::println(\"{}\", verboseString(\"git submodule update --init returned: {}\", ret));\n\n            break;\n        }\n    }\n\n    progress.m_szCurrentMessage = \"Verifying headers\";\n    progress.print();\n\n    const auto HEADERSSTATUS = headersValid();\n\n    if (HEADERSSTATUS != HEADERS_OK) {\n        std::println(\"\\n{}\", headerError(HEADERSSTATUS));\n        std::println(\"\\n{}\", infoString(\"if the problem persists, try running hyprpm purge-cache.\"));\n        return false;\n    }\n\n    progress.m_iSteps = 3;\n    progress.printMessageAbove(successString(\"Hyprland headers OK\"));\n    progress.m_szCurrentMessage = \"Building plugin(s)\";\n    progress.print();\n\n    for (auto& p : pManifest->m_plugins) {\n        std::string out;\n\n        if (p.since > HLVER.commits && HLVER.commits >= 1 /* for --depth 1 clones, we can't check this. */) {\n            progress.printMessageAbove(failureString(\"Not building {}: your Hyprland version is too old.\\n\", p.name));\n            p.failed = true;\n            continue;\n        }\n\n        progress.printMessageAbove(infoString(\"Building {}\", p.name));\n\n        for (auto const& bs : p.buildSteps) {\n            const auto CMD_RAW = nixDevelopIfNeeded(std::format(\"cd {} && PKG_CONFIG_PATH=\\\"{}\\\" {}\", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);\n\n            if (!CMD_RAW) {\n                progress.printMessageAbove(failureString(\"Failed to build {}: {}\", p.name, CMD_RAW.error()));\n                break;\n            }\n\n            out += \" -> \" + *CMD_RAW + \"\\n\" + execAndGet(*CMD_RAW) + \"\\n\";\n        }\n\n        if (m_bVerbose)\n            std::println(\"{}\", verboseString(\"shell returned: {}\", out));\n\n        if (!std::filesystem::exists(m_szWorkingPluginDirectory + \"/\" + p.output)) {\n            progress.printMessageAbove(failureString(\"Plugin {} failed to build.\\n\"\n                                                     \"  This likely means that the plugin is either outdated, not yet available for your version, or broken.\\n\"\n                                                     \"  If you are on -git, update first\\n\"\n                                                     \"  Try re-running with -v to see more verbose output.\\n\",\n                                                     p.name));\n\n            p.failed = true;\n            continue;\n        }\n\n        progress.printMessageAbove(successString(\"built {} into {}\", p.name, p.output));\n    }\n\n    progress.printMessageAbove(successString(\"all plugins built\"));\n    progress.m_iSteps           = 4;\n    progress.m_szCurrentMessage = \"Installing repository\";\n    progress.print();\n\n    // add repo toml to DataState\n    SPluginRepository repo;\n    std::string       repohash = execAndGet(\"cd \" + m_szWorkingPluginDirectory + \" && git rev-parse HEAD\");\n    if (repohash.length() > 0)\n        repohash.pop_back();\n    auto lastSlash       = url.find_last_of('/');\n    auto secondLastSlash = url.find_last_of('/', lastSlash - 1);\n    repo.name            = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name;\n    repo.author          = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1);\n    repo.url             = url;\n    repo.rev             = rev;\n    repo.hash            = repohash;\n    for (auto const& p : pManifest->m_plugins) {\n        repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + \"/\" + p.output, false, p.failed});\n    }\n    DataState::addNewPluginRepo(repo);\n\n    progress.printMessageAbove(successString(\"installed repository\"));\n    progress.printMessageAbove(successString(\"you can now enable the plugin(s) with hyprpm enable\"));\n    progress.m_iSteps           = 5;\n    progress.m_szCurrentMessage = \"Done!\";\n    progress.print();\n\n    std::print(\"\\n\");\n\n    // remove build files\n    std::filesystem::remove_all(m_szWorkingPluginDirectory);\n\n    return true;\n}\n\nbool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) {\n    if (!DataState::pluginRepoExists(identifier)) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not remove the repository. Repository is not installed.\"));\n        return false;\n    }\n\n    std::cout << Colors::YELLOW << \"!\" << Colors::RESET << Colors::RED << \" removing a plugin repository: \" << Colors::RESET << identifier.toString() << \"\\n  \"\n              << \"Are you sure? [Y/n] \";\n    std::fflush(stdout);\n    std::string input;\n    std::getline(std::cin, input);\n\n    if (input.size() > 0 && input[0] != 'Y' && input[0] != 'y') {\n        std::println(\"Aborting.\");\n        return false;\n    }\n\n    DataState::removePluginRepo(identifier);\n\n    return true;\n}\n\neHeadersErrors CPluginManager::headersValid() {\n    const auto HLVER = getHyprlandVersion(false);\n\n    if (!std::filesystem::exists(DataState::getHeadersPath() + \"/share/pkgconfig/hyprland.pc\"))\n        return HEADERS_MISSING;\n\n    // find headers commit\n    const std::string& cmd     = std::format(\"PKG_CONFIG_PATH=\\\"{}\\\" pkgconf --cflags --keep-system-cflags hyprland\", getPkgConfigPath());\n    auto               headers = execAndGet(cmd);\n\n    if (!headers.contains(\"-I/\"))\n        return HEADERS_MISSING;\n\n    headers.pop_back(); // pop newline\n\n    std::string verHeader;\n\n    while (!headers.empty()) {\n        const auto PATH = headers.substr(0, headers.find(\" -I/\", 3));\n\n        if (headers.find(\" -I/\", 3) != std::string::npos)\n            headers = headers.substr(headers.find(\"-I/\", 3));\n        else\n            headers = \"\";\n\n        if (PATH.ends_with(\"protocols\"))\n            continue;\n\n        verHeader = trim(PATH.substr(2)) + \"/hyprland/src/version.h\";\n        break;\n    }\n\n    if (verHeader.empty())\n        return HEADERS_CORRUPTED;\n\n    // read header\n    std::ifstream ifs(verHeader);\n    if (!ifs.good())\n        return HEADERS_CORRUPTED;\n\n    std::string verHeaderContent((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));\n    ifs.close();\n\n    const auto HASHPOS = verHeaderContent.find(\"#define GIT_COMMIT_HASH\");\n\n    if (HASHPOS == std::string::npos || HASHPOS + 23 >= verHeaderContent.length())\n        return HEADERS_CORRUPTED;\n\n    std::string hash = verHeaderContent.substr(HASHPOS + 23);\n    hash             = hash.substr(0, hash.find_first_of('\\n'));\n    hash             = hash.substr(hash.find_first_of('\"') + 1);\n    hash             = hash.substr(0, hash.find_first_of('\"'));\n\n    if (hash != HLVER.hash)\n        return HEADERS_MISMATCHED;\n\n    // check ABI hash too\n    const auto GLOBALSTATE = DataState::getGlobalState();\n\n    if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash)\n        return HEADERS_ABI_MISMATCH;\n\n    return HEADERS_OK;\n}\n\nbool CPluginManager::updateHeaders(bool force) {\n    DataState::ensureStateStoreExists();\n\n    const auto HLVER = getHyprlandVersion(false);\n\n    if (!hasDeps()) {\n        std::println(\"\\n{}\", failureString(\"Could not update. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc\"));\n        return false;\n    }\n\n    if (!std::filesystem::exists(getTempRoot())) {\n        std::filesystem::create_directory(getTempRoot());\n        std::filesystem::permissions(getTempRoot(), std::filesystem::perms::owner_all, std::filesystem::perm_options::replace);\n    }\n\n    if (!force && headersValid() == HEADERS_OK) {\n        std::println(\"\\n{}\", successString(\"Headers up to date.\"));\n        return true;\n    }\n\n    CProgressBar progress;\n    progress.m_iMaxSteps        = 5;\n    progress.m_iSteps           = 0;\n    progress.m_szCurrentMessage = \"Cloning the hyprland repository\";\n    progress.print();\n\n    const std::string USERNAME   = getpwuid(getuid())->pw_name;\n    const auto        WORKINGDIR = getTempRoot() + \"hyprland-\" + USERNAME;\n\n    if (!createSafeDirectory(WORKINGDIR)) {\n        std::println(\"\\n{}\", failureString(\"Could not prepare working dir for hl\"));\n        return false;\n    }\n\n    const auto& HL_URL = m_szCustomHlUrl.empty() ? \"https://github.com/hyprwm/Hyprland\" : m_szCustomHlUrl;\n\n    progress.printMessageAbove(statusString(\"!\", Colors::YELLOW, \"Cloning {}, this might take a moment.\", HL_URL));\n\n    const bool bShallow = (HLVER.branch == \"main\") && !m_bNoShallow;\n\n    // let us give a bit of leg-room for shallowing\n    // due to timezones, etc.\n    const std::string SHALLOW_DATE = trim(HLVER.date).empty() ? \"\" : execAndGet(\"LC_TIME=\\\"en_US.UTF-8\\\" date --date='\" + HLVER.date + \" - 1 weeks' '+%a %b %d %H:%M:%S %Y'\");\n\n    if (m_bVerbose && bShallow)\n        progress.printMessageAbove(verboseString(\"will shallow since: {}\", SHALLOW_DATE));\n\n    std::string ret =\n        execAndGet(std::format(\"cd {} && git clone --recursive '{}' hyprland-{}{}\", getTempRoot(), HL_URL, USERNAME, (bShallow ? \" --shallow-since='\" + SHALLOW_DATE + \"'\" : \"\")));\n\n    if (!std::filesystem::exists(WORKINGDIR)) {\n        progress.printMessageAbove(failureString(\"Clone failed. Retrying without shallow.\"));\n        ret = execAndGet(std::format(\"cd {} && git clone --recursive '{}' hyprland-{}\", getTempRoot(), HL_URL, USERNAME));\n    }\n\n    if (!std::filesystem::exists(WORKINGDIR + \"/.git\")) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not clone the Hyprland repository. shell returned:\\n{}\", ret));\n        return false;\n    }\n\n    progress.printMessageAbove(successString(\"Hyprland cloned\"));\n    progress.m_iSteps           = 2;\n    progress.m_szCurrentMessage = \"Checking out sources\";\n    progress.print();\n\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"will run: cd {} && git checkout {} 2>&1\", WORKINGDIR, HLVER.hash));\n\n    ret = execAndGet(\"cd \" + WORKINGDIR + \" && git checkout \" + HLVER.hash + \" 2>&1\");\n\n    if (ret.contains(\"fatal: unable to read tree\")) {\n        std::println(stderr, \"\\n{}\",\n                     failureString(\"Could not checkout the running Hyprland commit. If you are on -git, try updating.\\n\"\n                                   \"You can also try re-running hyprpm update with --no-shallow.\"));\n        return false;\n    }\n\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"git returned (co): {}\", ret));\n\n    ret = execAndGet(\"cd \" + WORKINGDIR + \" ; git rm subprojects/tracy ; git submodule update --init 2>&1 ; git reset --hard --recurse-submodules \" + HLVER.hash);\n\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"git returned (rs): {}\", ret));\n\n    progress.printMessageAbove(successString(\"checked out to running ver\"));\n    progress.m_iSteps           = 3;\n    progress.m_szCurrentMessage = \"Building Hyprland\";\n    progress.print();\n\n    progress.printMessageAbove(statusString(\"!\", Colors::YELLOW, \"configuring Hyprland\"));\n\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"setting PREFIX for cmake to {}\", DataState::getHeadersPath()));\n\n    const auto CONFIGURE_CMD =\n        nixDevelopIfNeeded(std::format(\"cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\\\"{}\\\" -S . -B ./build\", WORKINGDIR,\n                                       DataState::getHeadersPath()),\n                           HLVER);\n\n    if (!CONFIGURE_CMD) {\n        std::println(stderr, \"\\n{}\", failureString(\"Could not configure hyprland: {}\", CONFIGURE_CMD.error()));\n        return false;\n    }\n\n    ret = execAndGet(*CONFIGURE_CMD);\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"cmake returned: {}\", ret));\n\n    if (ret.contains(\"CMake Error at\")) {\n        // missing deps, let the user know.\n        std::string missing = ret.substr(ret.find(\"CMake Error at\"));\n        missing             = ret.substr(ret.find_first_of('\\n') + 1);\n        missing             = missing.substr(0, missing.find(\"-- Configuring incomplete\"));\n        missing             = missing.substr(0, missing.find_last_of('\\n'));\n\n        std::println(stderr, \"\\n{}\",\n                     failureString(\"Could not configure the hyprland source, cmake complained:\\n{}\\n\\n\"\n                                   \"This likely means that you are missing the above dependencies or they are out of date.\",\n                                   missing));\n        return false;\n    }\n\n    progress.printMessageAbove(successString(\"configured Hyprland\"));\n    progress.m_iSteps           = 4;\n    progress.m_szCurrentMessage = \"Installing sources\";\n    progress.print();\n\n    std::string cmd = std::format(\"sed -i -e \\\"s#PREFIX = /usr/local#PREFIX = {}#\\\" {}/Makefile\", DataState::getHeadersPath(), WORKINGDIR);\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"prepare install will run: {}\", cmd));\n\n    ret = execAndGet(cmd);\n\n    cmd = std::format(\"make -C '{}' installheaders && chmod -R 644 '{}' && find '{}' -type d -exec chmod a+x {{}} \\\\;\", WORKINGDIR, DataState::getHeadersPath(),\n                      DataState::getHeadersPath());\n\n    if (m_bVerbose)\n        progress.printMessageAbove(verboseString(\"install will run as sudo: {}\", cmd));\n\n    // WORKINGDIR and headersPath should not contain anything unsafe. Usernames can't contain cmd exec parts.\n    ret = NSys::root::runAsSuperuserUnsafe(cmd);\n\n    if (m_bVerbose)\n        std::println(\"{}\", verboseString(\"installer returned: {}\", ret));\n\n    // remove build files\n    std::filesystem::remove_all(WORKINGDIR);\n\n    auto HEADERSVALID = headersValid();\n\n    if (HEADERSVALID == HEADERS_OK || HEADERSVALID == HEADERS_MISMATCHED || HEADERSVALID == HEADERS_ABI_MISMATCH) {\n        progress.printMessageAbove(successString(\"installed headers\"));\n        progress.m_iSteps           = 5;\n        progress.m_szCurrentMessage = \"Done!\";\n        progress.print();\n\n        auto GLOBALSTATE               = DataState::getGlobalState();\n        GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;\n        DataState::updateGlobalState(GLOBALSTATE);\n\n        std::print(\"\\n\");\n    } else {\n        progress.printMessageAbove(failureString(\"failed to install headers with error code {} ({})\", sc<int>(HEADERSVALID), headerErrorShort(HEADERSVALID)));\n        progress.printMessageAbove(infoString(\"if the problem persists, try running hyprpm purge-cache.\"));\n        progress.m_iSteps           = 5;\n        progress.m_szCurrentMessage = \"Failed\";\n        progress.print();\n\n        std::print(stderr, \"\\n\\n{}\", headerError(HEADERSVALID));\n\n        return false;\n    }\n\n    return true;\n}\n\nbool CPluginManager::updatePlugins(bool forceUpdateAll) {\n    if (headersValid() != HEADERS_OK) {\n        std::println(\"{}\", failureString(\"headers are not up-to-date, please run hyprpm update.\"));\n        return false;\n    }\n\n    const auto REPOS = DataState::getAllRepositories();\n\n    if (REPOS.size() < 1) {\n        std::println(\"{}\", failureString(\"No repos to update.\"));\n        return true;\n    }\n\n    const auto   HLVER = getHyprlandVersion(false);\n\n    CProgressBar progress;\n    progress.m_iMaxSteps        = (REPOS.size() * 2) + 2;\n    progress.m_iSteps           = 0;\n    progress.m_szCurrentMessage = \"Updating repositories\";\n    progress.print();\n\n    const std::string USERNAME = getpwuid(getuid())->pw_name;\n    m_szWorkingPluginDirectory = getTempRoot() + USERNAME;\n\n    for (auto const& repo : REPOS) {\n        bool update = forceUpdateAll;\n\n        progress.m_iSteps++;\n        progress.m_szCurrentMessage = \"Updating \" + repo.name;\n        progress.print();\n\n        progress.printMessageAbove(infoString(\"checking for updates for {}\", repo.name));\n\n        createSafeDirectory(m_szWorkingPluginDirectory);\n\n        progress.printMessageAbove(infoString(\"Cloning {}\", repo.url));\n\n        std::string ret = execAndGet(std::format(\"cd {} && git clone --recursive '{}' {}\", getTempRoot(), repo.url, USERNAME));\n\n        if (!std::filesystem::exists(m_szWorkingPluginDirectory + \"/.git\")) {\n            std::println(\"{}\", failureString(\"could not clone repo: shell returned: {}\", ret));\n            return false;\n        }\n\n        if (!repo.rev.empty()) {\n            progress.printMessageAbove(infoString(\"Plugin has revision set, resetting: {}\", repo.rev));\n\n            std::string ret = execAndGet(\"git -C \" + m_szWorkingPluginDirectory + \" reset --hard --recurse-submodules \\'\" + repo.rev + \"\\'\");\n            if (ret.compare(0, 6, \"fatal:\") == 0) {\n                std::println(stderr, \"\\n{}\", failureString(\"could not check out revision {}: shell returned:\\n{}\", repo.rev, ret));\n\n                return false;\n            }\n        }\n\n        if (!update) {\n            // check if git has updates\n            std::string hash = execAndGet(\"cd \" + m_szWorkingPluginDirectory + \" && git rev-parse HEAD\");\n            if (!hash.empty())\n                hash.pop_back();\n\n            update = update || hash != repo.hash;\n        }\n\n        if (!update) {\n            std::filesystem::remove_all(m_szWorkingPluginDirectory);\n            progress.printMessageAbove(successString(\"repository {} is up-to-date.\", repo.name));\n            progress.m_iSteps++;\n            progress.print();\n            continue;\n        }\n\n        // we need to update\n\n        progress.printMessageAbove(successString(\"repository {} has updates.\", repo.name));\n        progress.printMessageAbove(infoString(\"Building {}\", repo.name));\n        progress.m_iSteps++;\n        progress.print();\n\n        std::unique_ptr<CManifest> pManifest;\n\n        if (std::filesystem::exists(m_szWorkingPluginDirectory + \"/hyprpm.toml\")) {\n            progress.printMessageAbove(successString(\"found hyprpm manifest\"));\n            pManifest = std::make_unique<CManifest>(MANIFEST_HYPRPM, m_szWorkingPluginDirectory + \"/hyprpm.toml\");\n        } else if (std::filesystem::exists(m_szWorkingPluginDirectory + \"/hyprload.toml\")) {\n            progress.printMessageAbove(successString(\"found hyprload manifest\"));\n            pManifest = std::make_unique<CManifest>(MANIFEST_HYPRLOAD, m_szWorkingPluginDirectory + \"/hyprload.toml\");\n        }\n\n        if (!pManifest) {\n            std::println(stderr, \"\\n{}\", failureString(\"The provided plugin repository does not have a valid manifest\"));\n            continue;\n        }\n\n        if (!pManifest->m_good) {\n            std::println(stderr, \"\\n{}\", failureString(\"The provided plugin repository has a bad manifest\"));\n            continue;\n        }\n\n        if (repo.rev.empty() && !pManifest->m_repository.commitPins.empty()) {\n            // check commit pins unless a revision is specified\n\n            progress.printMessageAbove(infoString(\"Manifest has {} pins, checking\", pManifest->m_repository.commitPins.size()));\n\n            for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) {\n                if (hl != HLVER.hash)\n                    continue;\n\n                progress.printMessageAbove(successString(\"commit pin {} matched hl, resetting\", plugin));\n\n                execAndGet(\"cd \" + m_szWorkingPluginDirectory + \" && git reset --hard --recurse-submodules \" + plugin);\n            }\n        }\n\n        for (auto& p : pManifest->m_plugins) {\n            std::string out;\n\n            if (p.since > HLVER.commits && HLVER.commits >= 1000 /* for shallow clones, we can't check this. 1000 is an arbitrary number I chose. */) {\n                progress.printMessageAbove(failureString(\"Not building {}: your Hyprland version is too old.\\n\", p.name));\n                p.failed = true;\n                continue;\n            }\n\n            progress.printMessageAbove(infoString(\"Building {}\", p.name));\n\n            for (auto const& bs : p.buildSteps) {\n                const auto CMD_RAW = nixDevelopIfNeeded(std::format(\"cd {} && PKG_CONFIG_PATH=\\\"{}\\\" {}\", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);\n\n                if (!CMD_RAW) {\n                    progress.printMessageAbove(failureString(\"Failed to build {}: {}\", p.name, CMD_RAW.error()));\n                    break;\n                }\n\n                out += \" -> \" + *CMD_RAW + \"\\n\" + execAndGet(*CMD_RAW) + \"\\n\";\n            }\n\n            if (m_bVerbose)\n                std::println(\"{}\", verboseString(\"shell returned: {}\", out));\n\n            if (!std::filesystem::exists(m_szWorkingPluginDirectory + \"/\" + p.output)) {\n                std::println(stderr,\n                             \"\\n{}\\n\"\n                             \"  This likely means that the plugin is either outdated, not yet available for your version, or broken.\\n\"\n                             \"If you are on -git, update first.\\n\"\n                             \"Try re-running with -v to see more verbose output.\",\n                             failureString(\"Plugin {} failed to build.\", p.name));\n                p.failed = true;\n                continue;\n            }\n\n            progress.printMessageAbove(successString(\"built {} into {}\", p.name, p.output));\n        }\n\n        // add repo toml to DataState\n        SPluginRepository newrepo = repo;\n        newrepo.plugins.clear();\n        execAndGet(\"cd \" + m_szWorkingPluginDirectory +\n                   \" && git pull --recurse-submodules && git reset --hard --recurse-submodules\"); // repo hash in the state.toml has to match head and not any pin\n        std::string repohash = execAndGet(\"cd \" + m_szWorkingPluginDirectory + \" && git rev-parse HEAD\");\n        if (repohash.length() > 0)\n            repohash.pop_back();\n        newrepo.hash = repohash;\n        for (auto const& p : pManifest->m_plugins) {\n            const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; });\n            newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + \"/\" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});\n        }\n        DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));\n        DataState::addNewPluginRepo(newrepo);\n\n        std::filesystem::remove_all(m_szWorkingPluginDirectory);\n\n        progress.printMessageAbove(successString(\"updated {}\", repo.name));\n    }\n\n    progress.m_iSteps++;\n    progress.m_szCurrentMessage = \"Updating global state...\";\n    progress.print();\n\n    auto GLOBALSTATE               = DataState::getGlobalState();\n    GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;\n    DataState::updateGlobalState(GLOBALSTATE);\n\n    progress.m_iSteps++;\n    progress.m_szCurrentMessage = \"Done!\";\n    progress.print();\n\n    std::print(\"\\n\");\n\n    return true;\n}\n\nbool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) {\n    bool ret = false;\n\n    switch (identifier.type) {\n        case IDENTIFIER_NAME:\n        case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break;\n        default: return false;\n    }\n    if (ret)\n        std::println(\"{}\", successString(\"Enabled {}\", identifier.name));\n    return ret;\n}\n\nbool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) {\n    bool ret = DataState::setPluginEnabled(identifier, false);\n    if (ret)\n        std::println(\"{}\", successString(\"Disabled {}\", identifier.name));\n    return ret;\n}\n\nePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) {\n    if (headersValid() != HEADERS_OK) {\n        std::println(stderr, \"\\n{}\", failureString(\"headers are not up-to-date, please run hyprpm update.\"));\n        return LOADSTATE_HEADERS_OUTDATED;\n    }\n\n    const auto HOME = getenv(\"HOME\");\n    const auto HIS  = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n    if (!HOME || !HIS) {\n        std::println(stderr, \"PluginManager: no $HOME or $HYPRLAND_INSTANCE_SIGNATURE\");\n        return LOADSTATE_FAIL;\n    }\n    const auto HYPRPMPATH = DataState::getDataStatePath();\n\n    const auto json = glz::read_json<glz::generic::array_t>(NHyprlandSocket::send(\"j/plugins list\"));\n    if (!json) {\n        std::println(stderr, \"PluginManager: couldn't parse plugin list output\");\n        return LOADSTATE_FAIL;\n    }\n\n    std::vector<std::string> loadedPlugins;\n    for (const auto& plugin : json.value()) {\n        if (!plugin.is_object() || !plugin.contains(\"name\")) {\n            std::println(stderr, \"PluginManager: couldn't parse plugin object\");\n            return LOADSTATE_FAIL;\n        }\n        loadedPlugins.emplace_back(plugin[\"name\"].get<std::string>());\n    }\n\n    std::println(\"{}\", successString(\"Ensuring plugin load state\"));\n\n    // get state\n    const auto REPOS = DataState::getAllRepositories();\n\n    auto       enabled = [REPOS](const std::string& plugin) -> bool {\n        for (auto const& r : REPOS) {\n            for (auto const& p : r.plugins) {\n                if (p.name == plugin && p.enabled)\n                    return true;\n            }\n        }\n\n        return false;\n    };\n\n    auto repoForName = [REPOS](const std::string& name) -> std::string {\n        for (auto const& r : REPOS) {\n            for (auto const& p : r.plugins) {\n                if (p.name == name)\n                    return r.name;\n            }\n        }\n\n        return \"\";\n    };\n\n    // if any of the loadUnloadPlugin calls return false, this is true\n    // bcs that means the header version doesn't match the running version\n    // (and Hyprland needs to restart)\n    bool hyprlandVersionMismatch = false;\n\n    // unload disabled plugins (or all if forceReload is true)\n    for (auto const& p : loadedPlugins) {\n        if (forceReload || !enabled(p)) {\n            // unload\n            if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p) / (p + \".so\"), false)) {\n                std::println(\"{}\", infoString(\"{} will be unloaded after restarting Hyprland\", p));\n                hyprlandVersionMismatch = true;\n            } else\n                std::println(\"{}\", successString(\"Unloaded {}\", p));\n        }\n    }\n\n    // load enabled plugins\n    for (auto const& r : REPOS) {\n        for (auto const& p : r.plugins) {\n            if (!p.enabled)\n                continue;\n\n            if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())\n                continue;\n\n            if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) {\n                std::println(\"{}\", infoString(\"{} will be loaded after restarting Hyprland\", p.name));\n                hyprlandVersionMismatch = true;\n            } else\n                std::println(\"{}\", successString(\"Loaded {}\", p.name));\n        }\n    }\n\n    std::println(\"{}\", successString(\"Plugin load state ensured\"));\n\n    return hyprlandVersionMismatch ? LOADSTATE_HYPRLAND_UPDATED : LOADSTATE_OK;\n}\n\nbool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {\n    auto state = DataState::getGlobalState();\n    auto HLVER = getHyprlandVersion(true);\n\n    if (state.headersAbiCompiled != HLVER.abiHash) {\n        if (load)\n            std::println(\"{}\", infoString(\"Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.\", HLVER.hash, state.headersAbiCompiled));\n        return false;\n    }\n\n    if (load)\n        NHyprlandSocket::send(\"/plugin load \" + path);\n    else\n        NHyprlandSocket::send(\"/plugin unload \" + path);\n\n    return true;\n}\n\nvoid CPluginManager::listAllPlugins() {\n    const auto REPOS = DataState::getAllRepositories();\n\n    for (auto const& r : REPOS) {\n        std::println(\"{}\", infoString(\"Repository {} (by {}):\", r.name, r.author));\n\n        for (auto const& p : r.plugins) {\n            std::println(\"  │ Plugin {}\", p.name);\n\n            if (!p.failed)\n                std::println(\"  └─ enabled: {}\", (p.enabled ? std::string{Colors::GREEN} + \"true\" : std::string{Colors::RED} + \"false\"));\n            else\n                std::println(\"  └─ enabled: {}Plugin failed to build\", Colors::RED);\n\n            std::println(\"{}\", Colors::RESET);\n        }\n    }\n}\n\nvoid CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) {\n    NHyprlandSocket::send(\"/notify \" + std::to_string(icon) + \" \" + std::to_string(durationMs) + \" \" + std::to_string(color) + \" \" + message);\n}\n\nstd::string CPluginManager::headerError(const eHeadersErrors err) {\n    switch (err) {\n        case HEADERS_CORRUPTED: return failureString(\"Headers corrupted. Please run hyprpm update to fix those.\\n\");\n        case HEADERS_MISMATCHED: return failureString(\"Headers version mismatch. Please run hyprpm update to fix those.\\n\");\n        case HEADERS_NOT_HYPRLAND: return failureString(\"It doesn't seem you are running on hyprland.\\n\");\n        case HEADERS_MISSING: return failureString(\"Headers missing. Please run hyprpm update to fix those.\\n\");\n        case HEADERS_ABI_MISMATCH: return failureString(\"ABI is mismatched. Please run hyprpm update to fix that.\\n\");\n        case HEADERS_DUPLICATED: {\n            return failureString(\"Headers duplicated!!! This is a very bad sign.\\n\"\n                                 \"This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\\n\"\n                                 \"If the above doesn't apply, check your /usr/include and /usr/local/include directories\\n and remove all the hyprland headers.\\n\");\n        }\n        default: break;\n    }\n\n    return failureString(\"Unknown header error. Please run hyprpm update to fix those.\\n\");\n}\n\nstd::string CPluginManager::headerErrorShort(const eHeadersErrors err) {\n    switch (err) {\n        case HEADERS_CORRUPTED: return \"Headers corrupted\";\n        case HEADERS_MISMATCHED: return \"Headers version mismatched\";\n        case HEADERS_NOT_HYPRLAND: return \"Not running on Hyprland\";\n        case HEADERS_MISSING: return \"Headers missing\";\n        case HEADERS_DUPLICATED: return \"Headers duplicated\";\n        default: break;\n    }\n    return \"?\";\n}\n\nbool CPluginManager::hasDeps() {\n    if (!m_bNoNix && getHyprlandVersion().isNix)\n        return true; // dep check not needed if we are on nix\n\n    bool                     hasAllDeps = true;\n    std::vector<std::string> deps       = {\"cpio\", \"cmake\", \"pkg-config\", \"g++\", \"gcc\", \"git\"};\n\n    for (auto const& d : deps) {\n        if (!execAndGet(\"command -v \" + d).contains(\"/\")) {\n            std::println(stderr, \"{}\", failureString(\"Missing dependency: {}\", d));\n            hasAllDeps = false;\n        }\n    }\n\n    return hasAllDeps;\n}\n\nconst std::string& CPluginManager::getPkgConfigPath() {\n    static const auto str = std::format(\"{}/share/pkgconfig:$PKG_CONFIG_PATH\", DataState::getHeadersPath());\n    return str;\n}\n\nstatic std::expected<std::string, std::string> getNixDevelopFromPath(const std::string& argv0) {\n    std::string fullStorePath;\n\n    if (argv0.starts_with(\"/\")) {\n        // we can use this directly\n        fullStorePath = argv0;\n    } else {\n        // use hyprpm, find in path\n        auto exe = NSys::findInPath(\"hyprpm\");\n        if (!exe)\n            return std::unexpected(\"hyprpm not found in PATH\");\n\n        fullStorePath = *exe;\n    }\n\n    if (fullStorePath.empty() || !fullStorePath.ends_with(\"/bin/hyprpm\"))\n        return std::unexpected(\"couldn't get a real path for hyprpm (1)\");\n\n    // canonicalize to get the real nix-store path\n    std::error_code ec;\n    fullStorePath = std::filesystem::canonical(fullStorePath, ec);\n\n    if (ec || fullStorePath.empty() || !fullStorePath.starts_with(\"/nix\"))\n        return std::unexpected(\"couldn't get a real path for hyprpm\");\n\n    fullStorePath = fullStorePath.substr(0, fullStorePath.length() - std::string_view{\"/bin/hyprpm\"}.length());\n\n    auto deriver = trim(execAndGet(std::format(\"echo \\\"$(nix-store --query --deriver '{}')\\\"\", fullStorePath)));\n\n    if (deriver.starts_with(\"unknown\"))\n        return std::unexpected(\"couldn't nix deriver\");\n\n    return deriver;\n}\n\nstatic std::expected<std::string, std::string> getNixDevelopFromProfile() {\n    const auto NIX_PROFILE_STR = execAndGet(\"nix profile list --json\");\n\n    auto       rawJson = glz::read_json<glz::generic>(NIX_PROFILE_STR);\n\n    if (!rawJson)\n        return std::unexpected(\"failed to parse nix profile list --json\");\n\n    auto& json = *rawJson;\n\n    if (!json.contains(\"elements\") || !json[\"elements\"].is_object())\n        return std::unexpected(\"nix profile list --json returned a wonky json\");\n\n    if (!json[\"elements\"].contains(\"hyprland\") && !json[\"elements\"].contains(\"Hyprland\"))\n        return std::unexpected(\"nix profile list --json doesn't contain Hyprland (did you uninstall?)\");\n\n    auto& hyprlandJson = json[\"elements\"].contains(\"hyprland\") ? json[\"elements\"][\"hyprland\"] : json[\"elements\"][\"Hyprland\"];\n\n    if (!hyprlandJson.contains(\"originalUrl\"))\n        return std::unexpected(\"nix profile list --json's hyprland doesn't contain originalUrl?\");\n\n    return hyprlandJson[\"originalUrl\"].get_string();\n}\n\nstd::expected<std::string, std::string> CPluginManager::nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver) {\n    if (m_bNoNix || !ver.isNix)\n        return cmd;\n\n    // Escape single quotes\n    std::string newCmd = cmd;\n    replaceInString(newCmd, \"'\", \"\\\\'\");\n\n    auto NIX_DEVELOP = getNixDevelopFromPath(m_szArgv0);\n\n    if (NIX_DEVELOP)\n        return std::format(\"nix develop '{}' --command bash -c $'{}'\", *NIX_DEVELOP, newCmd);\n    else if (m_bVerbose)\n        std::println(\"{}\", verboseString(\"Failed nix from path: {}\", NIX_DEVELOP.error()));\n\n    NIX_DEVELOP = getNixDevelopFromProfile();\n\n    if (NIX_DEVELOP)\n        return std::format(\"nix develop '{}' --command bash -c $'{}'\", *NIX_DEVELOP, newCmd);\n    else if (m_bVerbose)\n        std::println(\"{}\", verboseString(\"Failed nix from profile: {}\", NIX_DEVELOP.error()));\n\n    return std::unexpected(\"hyprland is nix, but hyprpm failed to obtain a nix develop shell for build cmd\");\n}\n"
  },
  {
    "path": "hyprpm/src/core/PluginManager.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <string>\n#include <expected>\n#include \"Plugin.hpp\"\n\nenum eHeadersErrors {\n    HEADERS_OK = 0,\n    HEADERS_NOT_HYPRLAND,\n    HEADERS_MISSING,\n    HEADERS_CORRUPTED,\n    HEADERS_MISMATCHED,\n    HEADERS_ABI_MISMATCH,\n    HEADERS_DUPLICATED\n};\n\nenum eNotifyIcons {\n    ICON_WARNING = 0,\n    ICON_INFO,\n    ICON_HINT,\n    ICON_ERROR,\n    ICON_CONFUSED,\n    ICON_OK,\n    ICON_NONE\n};\n\nenum ePluginLoadStateReturn {\n    LOADSTATE_OK = 0,\n    LOADSTATE_FAIL,\n    LOADSTATE_PARTIAL_FAIL,\n    LOADSTATE_HEADERS_OUTDATED,\n    LOADSTATE_HYPRLAND_UPDATED\n};\n\nstruct SHyprlandVersion {\n    std::string branch;\n    std::string hash;\n    std::string date;\n    std::string abiHash;\n    int         commits = 0;\n    bool        isNix   = false;\n};\n\nclass CPluginManager {\n  public:\n    CPluginManager();\n\n    bool                   addNewPluginRepo(const std::string& url, const std::string& rev);\n    bool                   removePluginRepo(const SPluginRepoIdentifier identifier);\n\n    eHeadersErrors         headersValid();\n    bool                   updateHeaders(bool force = false);\n    bool                   updatePlugins(bool forceUpdateAll);\n\n    void                   listAllPlugins();\n\n    bool                   enablePlugin(const SPluginRepoIdentifier identifier);\n    bool                   disablePlugin(const SPluginRepoIdentifier identifier);\n    ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false);\n\n    bool                   loadUnloadPlugin(const std::string& path, bool load);\n    SHyprlandVersion       getHyprlandVersion(bool running = true);\n\n    void                   notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message);\n\n    const std::string&     getPkgConfigPath();\n\n    bool                   hasDeps();\n\n    bool                   m_bVerbose   = false;\n    bool                   m_bNoShallow = false;\n    bool                   m_bNoNix     = false;\n    std::string            m_szCustomHlUrl, m_szUsername, m_szArgv0;\n\n    // will delete recursively if exists!!\n    bool createSafeDirectory(const std::string& path);\n\n  private:\n    std::string                             headerError(const eHeadersErrors err);\n    std::string                             headerErrorShort(const eHeadersErrors err);\n    bool                                    validArg(const std::string& s);\n\n    std::expected<std::string, std::string> nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver);\n\n    std::string                             m_szWorkingPluginDirectory;\n};\n\ninline std::unique_ptr<CPluginManager> g_pPluginManager;\n"
  },
  {
    "path": "hyprpm/src/helpers/Colors.hpp",
    "content": "#pragma once\n\nnamespace Colors {\n    constexpr const char* RED     = \"\\x1b[31m\";\n    constexpr const char* GREEN   = \"\\x1b[32m\";\n    constexpr const char* YELLOW  = \"\\x1b[33m\";\n    constexpr const char* BLUE    = \"\\x1b[34m\";\n    constexpr const char* MAGENTA = \"\\x1b[35m\";\n    constexpr const char* CYAN    = \"\\x1b[36m\";\n    constexpr const char* RESET   = \"\\x1b[0m\";\n};"
  },
  {
    "path": "hyprpm/src/helpers/Die.hpp",
    "content": "#pragma once\n\n#include <format>\n#include <iostream>\n\n// NOLINTNEXTLINE\nnamespace Debug {\n    template <typename... Args>\n    void die(std::format_string<Args...> fmt, Args&&... args) {\n        const std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));\n\n        std::cout << \"\\n[ERR] \" << logMsg << \"\\n\";\n        exit(1);\n    }\n};"
  },
  {
    "path": "hyprpm/src/helpers/StringUtils.hpp",
    "content": "#pragma once\n\n#include <format>\n#include <string>\n#include \"Colors.hpp\"\n\ntemplate <typename... Args>\nstd::string statusString(const std::string_view emoji, const std::string_view color, const std::string_view fmt, Args&&... args) {\n    std::string ret = std::format(\"{}{}{} \", color, emoji, Colors::RESET);\n    ret += std::vformat(fmt, std::make_format_args(args...));\n    return ret;\n}\n\ntemplate <typename... Args>\nstd::string successString(const std::string_view fmt, Args&&... args) {\n    return statusString(\"✔\", Colors::GREEN, fmt, args...);\n}\n\ntemplate <typename... Args>\nstd::string failureString(const std::string_view fmt, Args&&... args) {\n    return statusString(\"✖\", Colors::RED, fmt, args...);\n}\n\ntemplate <typename... Args>\nstd::string verboseString(const std::string_view fmt, Args&&... args) {\n    return statusString(\"[v]\", Colors::BLUE, fmt, args...);\n}\n\ntemplate <typename... Args>\nstd::string infoString(const std::string_view fmt, Args&&... args) {\n    return statusString(\"→\", Colors::RESET, fmt, args...);\n}\n"
  },
  {
    "path": "hyprpm/src/helpers/Sys.cpp",
    "content": "#include \"Sys.hpp\"\n#include \"Die.hpp\"\n#include \"StringUtils.hpp\"\n\n#include <pwd.h>\n#include <unistd.h>\n#include <sstream>\n#include <print>\n#include <filesystem>\n#include <algorithm>\n#include <sstream>\n\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/string/VarList.hpp>\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::String;\n\ninline constexpr std::array<std::string_view, 3> SUPERUSER_BINARIES = {\n    \"sudo\",\n    \"doas\",\n    \"run0\",\n};\n\nstatic std::string validSubinsAsStr() {\n    std::ostringstream oss;\n    auto               it = SUPERUSER_BINARIES.begin();\n    if (it != SUPERUSER_BINARIES.end()) {\n        oss << *it++;\n        for (; it != SUPERUSER_BINARIES.end(); ++it)\n            oss << \", \" << *it;\n    }\n\n    return oss.str();\n}\n\nstatic bool executableExistsInPath(const std::string& exe) {\n    return NSys::findInPath(exe).has_value();\n}\n\nstd::optional<std::string> NSys::findInPath(const std::string& exe) {\n    const char* PATHENV = std::getenv(\"PATH\");\n    if (!PATHENV)\n        return std::nullopt;\n\n    CVarList        paths(PATHENV, 0, ':', true);\n    std::error_code ec;\n\n    for (const auto& PATH : paths) {\n        std::filesystem::path candidate = std::filesystem::path(PATH) / exe;\n        if (!std::filesystem::exists(candidate, ec) || ec)\n            continue;\n        if (!std::filesystem::is_regular_file(candidate, ec) || ec)\n            continue;\n        auto perms = std::filesystem::status(candidate, ec).permissions();\n        if (ec)\n            continue;\n        if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)\n            return candidate.string();\n    }\n\n    return std::nullopt;\n}\n\nstatic std::string subin() {\n    static std::string bin;\n    static bool        once = true;\n    if (!once)\n        return bin;\n\n    for (const auto& BIN : SUPERUSER_BINARIES) {\n        if (!executableExistsInPath(std::string{BIN}))\n            continue;\n\n        bin = BIN;\n        break;\n    }\n\n    once = false;\n\n    if (bin.empty())\n        Debug::die(\"{}\", failureString(\"No valid superuser binary present. Supported: {}\", validSubinsAsStr()));\n\n    return bin;\n}\n\nstatic bool verifyStringValid(const std::string& s) {\n    return std::ranges::none_of(s, [](const char& c) { return c == '`' || c == '$' || c == '(' || c == ')' || c == '\\'' || c == '\"'; });\n}\n\nint NSys::getUID() {\n    const auto UID   = getuid();\n    const auto PWUID = getpwuid(UID);\n    return PWUID ? PWUID->pw_uid : UID;\n}\n\nint NSys::getEUID() {\n    const auto UID   = geteuid();\n    const auto PWUID = getpwuid(UID);\n    return PWUID ? PWUID->pw_uid : UID;\n}\n\nbool NSys::isSuperuser() {\n    return getuid() != geteuid() || geteuid() == 0;\n}\n\nvoid NSys::root::cacheSudo() {\n    // \"caches\" the sudo so that the prompt later doesn't pop up in a weird spot\n    // sudo will not ask us again for a moment\n    CProcess proc(subin(), {\"echo\", \"hyprland\"});\n    proc.runSync();\n}\n\nvoid NSys::root::dropSudo() {\n    if (subin() != \"sudo\") {\n        std::println(\"{}\", infoString(\"Don't know how to drop timestamp for '{}', ignoring.\", subin()));\n        return;\n    }\n\n    CProcess proc(subin(), {\"-k\"});\n    proc.runSync();\n}\n\nbool NSys::root::createDirectory(const std::string& path, const std::string& mode) {\n    if (!verifyStringValid(path))\n        return false;\n\n    if (!std::ranges::all_of(mode, [](const char& c) { return c >= '0' && c <= '9'; }))\n        return false;\n\n    CProcess proc(subin(), {\"mkdir\", \"-p\", \"-m\", mode, path});\n\n    return proc.runSync() && proc.exitCode() == 0;\n}\n\nbool NSys::root::removeRecursive(const std::string& path) {\n    if (!verifyStringValid(path))\n        return false;\n\n    std::error_code   ec;\n    const std::string PATH_ABSOLUTE = std::filesystem::canonical(path, ec);\n\n    if (ec)\n        return false;\n\n    if (!PATH_ABSOLUTE.starts_with(\"/var/cache/hyprpm\"))\n        return false;\n\n    CProcess proc(subin(), {\"rm\", \"-fr\", PATH_ABSOLUTE});\n\n    return proc.runSync() && proc.exitCode() == 0;\n}\n\nbool NSys::root::install(const std::string& what, const std::string& where, const std::string& mode) {\n    if (!verifyStringValid(what) || !verifyStringValid(where))\n        return false;\n\n    if (!std::ranges::all_of(mode, [](const char& c) { return c >= '0' && c <= '9'; }))\n        return false;\n\n    CProcess proc(subin(), {\"install\", \"-m\" + mode, \"-o\", \"0\", \"-g\", \"0\", what, where});\n\n    return proc.runSync() && proc.exitCode() == 0;\n}\n\nstd::string NSys::root::runAsSuperuserUnsafe(const std::string& cmd) {\n    CProcess proc(subin(), {\"/bin/sh\", \"-c\", cmd});\n\n    if (!proc.runSync())\n        return \"\";\n\n    return proc.stdOut();\n}\n"
  },
  {
    "path": "hyprpm/src/helpers/Sys.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <optional>\n\nnamespace NSys {\n    bool                       isSuperuser();\n    int                        getUID();\n    int                        getEUID();\n    std::optional<std::string> findInPath(const std::string& exe);\n\n    // NOLINTNEXTLINE\n    namespace root {\n        void cacheSudo();\n        void dropSudo();\n\n        //\n        [[nodiscard(\"Discarding could lead to vulnerabilities and bugs\")]] bool createDirectory(const std::string& path, const std::string& mode);\n        [[nodiscard(\"Discarding could lead to vulnerabilities and bugs\")]] bool removeRecursive(const std::string& path);\n        [[nodiscard(\"Discarding could lead to vulnerabilities and bugs\")]] bool install(const std::string& what, const std::string& where, const std::string& mode);\n\n        // Do not use this unless absolutely necessary!\n        std::string runAsSuperuserUnsafe(const std::string& cmd);\n    };\n};\n"
  },
  {
    "path": "hyprpm/src/main.cpp",
    "content": "#include \"helpers/Colors.hpp\"\n#include \"helpers/StringUtils.hpp\"\n#include \"core/PluginManager.hpp\"\n#include \"core/DataState.hpp\"\n#include \"helpers/Sys.hpp\"\n\n#include <vector>\n#include <string>\n#include <print>\n\n#include <hyprutils/utils/ScopeGuard.hpp>\nusing namespace Hyprutils::Utils;\n\nconstexpr std::string_view HELP = R\"#(┏ hyprpm, a Hyprland Plugin Manager\n┃\n┣ add <url> [git rev]           → Install a new plugin repository from git. Git revision\n┃                                 is optional, when set, commit locks are ignored.\n┣ remove <url|name|author/name> → Remove an installed plugin repository.\n┣ enable <name|author/name>     → Enable a plugin.\n┣ disable <name|author/name>    → Disable a plugin.\n┣ update                        → Check and update all plugins if needed.\n┣ reload                        → Reload hyprpm state. Ensure all enabled plugins are loaded.\n┣ list                          → List all installed plugins.\n┣ purge-cache                   → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.\n┃\n┣ Flags:\n┃\n┣ --no-nix       |              → Disable `nix develop` for build commands, even if Hyprland is nix.\n┣ --notify       | -n           → Send a hyprland notification confirming successful plugin load.\n┃                                 Warnings/Errors trigger notifications regardless of this flag.\n┣ --help         | -h           → Show this menu.\n┣ --verbose      | -v           → Enable too much logging.\n┣ --force        | -f           → Force an operation ignoring checks (e.g. update -f).\n┣ --no-shallow   | -s           → Disable shallow cloning of Hyprland sources.\n┣ --hl-url       |              → Pass a custom hyprland source url.\n┗\n)#\";\n\nint                        main(int argc, char** argv, char** envp) {\n    std::vector<std::string> ARGS{argc};\n    for (int i = 0; i < argc; ++i) {\n        ARGS[i] = std::string{argv[i]};\n    }\n\n    if (ARGS.size() < 2) {\n        std::println(stderr, \"{}\", HELP);\n        return 1;\n    }\n\n    std::vector<std::string> command;\n    bool                     notify = false, verbose = false, force = false, noShallow = false, noNix = false;\n    std::string              customHlUrl;\n\n    for (int i = 1; i < argc; ++i) {\n        if (ARGS[i].starts_with(\"-\")) {\n            if (ARGS[i] == \"--help\" || ARGS[i] == \"-h\") {\n                std::println(\"{}\", HELP);\n                return 0;\n            } else if (ARGS[i] == \"--notify\" || ARGS[i] == \"-n\") {\n                notify = true;\n            } else if (ARGS[i] == \"--notify-fail\" || ARGS[i] == \"-nn\") {\n                // TODO: Deprecated since v.053.0. Remove in version>0.56.0\n                std::println(stderr, \"{}\", failureString(\"Deprececated flag.\"));\n                g_pPluginManager->notify(ICON_INFO, 0, 10000, \"[hyprpm] -n flag is deprecated, see hyprpm --help.\");\n            } else if (ARGS[i] == \"--verbose\" || ARGS[i] == \"-v\") {\n                verbose = true;\n            } else if (ARGS[i] == \"--no-nix\") {\n                noNix = true;\n            } else if (ARGS[i] == \"--no-shallow\" || ARGS[i] == \"-s\") {\n                noShallow = true;\n            } else if (ARGS[i] == \"--hl-url\") {\n                if (i + 1 >= argc) {\n                    std::println(stderr, \"Missing argument for --hl-url\");\n                    return 1;\n                }\n                customHlUrl = ARGS[i + 1];\n                i++;\n            } else if (ARGS[i] == \"--force\" || ARGS[i] == \"-f\") {\n                force = true;\n                std::println(\"{}\", statusString(\"!\", Colors::RED, \"Using --force, I hope you know what you are doing.\"));\n            } else {\n                std::println(stderr, \"Unrecognized option {}\", ARGS[i]);\n                return 1;\n            }\n        } else\n            command.push_back(ARGS[i]);\n    }\n\n    if (command.empty()) {\n        std::println(stderr, \"{}\", HELP);\n        return 1;\n    }\n\n    g_pPluginManager                  = std::make_unique<CPluginManager>();\n    g_pPluginManager->m_bVerbose      = verbose;\n    g_pPluginManager->m_bNoShallow    = noShallow;\n    g_pPluginManager->m_bNoNix        = noNix;\n    g_pPluginManager->m_szCustomHlUrl = customHlUrl;\n    g_pPluginManager->m_szArgv0       = argv[0];\n\n    if (command[0] == \"add\") {\n        if (command.size() < 2) {\n            std::println(stderr, \"{}\", failureString(\"Not enough args for add.\"));\n            return 1;\n        }\n\n        std::string rev = \"\";\n        if (command.size() >= 3)\n            rev = command[2];\n\n        const auto HLVER       = g_pPluginManager->getHyprlandVersion();\n        auto       GLOBALSTATE = DataState::getGlobalState();\n\n        if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash) {\n            std::println(stderr, \"{}\", failureString(\"Headers outdated, please run hyprpm update.\"));\n            return 1;\n        }\n\n        NSys::root::cacheSudo();\n        CScopeGuard x([] { NSys::root::dropSudo(); });\n\n        g_pPluginManager->updateHeaders(false);\n\n        return g_pPluginManager->addNewPluginRepo(command[1], rev) ? 0 : 1;\n    } else if (command[0] == \"remove\") {\n        if (command.size() < 2) {\n            std::println(stderr, \"{}\", failureString(\"Not enough args for remove.\"));\n            return 1;\n        }\n\n        NSys::root::cacheSudo();\n        CScopeGuard x([] { NSys::root::dropSudo(); });\n\n        return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1;\n    } else if (command[0] == \"update\") {\n        NSys::root::cacheSudo();\n        CScopeGuard x([] { NSys::root::dropSudo(); });\n\n        bool        headersValid = g_pPluginManager->headersValid() == HEADERS_OK;\n        bool        headers      = g_pPluginManager->updateHeaders(force);\n\n        if (headers) {\n            const auto HLVER            = g_pPluginManager->getHyprlandVersion(false);\n            auto       GLOBALSTATE      = DataState::getGlobalState();\n            const auto COMPILEDOUTDATED = HLVER.abiHash != GLOBALSTATE.headersAbiCompiled;\n\n            bool       ret1 = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED);\n\n            if (!ret1)\n                return 1;\n\n            auto ret2 = g_pPluginManager->ensurePluginsLoadState();\n\n            if (ret2 == LOADSTATE_HYPRLAND_UPDATED)\n                g_pPluginManager->notify(ICON_INFO, 0, 10000, \"[hyprpm] Updated plugins, but Hyprland was updated. Please restart Hyprland.\");\n\n            if (ret2 != LOADSTATE_OK)\n                return 1;\n        } else {\n            g_pPluginManager->notify(ICON_ERROR, 0, 10000, \"[hyprpm] Couldn't update headers\");\n        }\n    } else if (command[0] == \"enable\") {\n        if (command.size() < 2) {\n            std::println(stderr, \"{}\", failureString(\"Not enough args for enable.\"));\n            return 1;\n        }\n\n        if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {\n            std::println(stderr, \"{}\", failureString(\"Couldn't enable plugin (missing?)\"));\n            return 1;\n        }\n\n        NSys::root::cacheSudo();\n        CScopeGuard x([] { NSys::root::dropSudo(); });\n\n        auto        ret = g_pPluginManager->ensurePluginsLoadState();\n\n        if (ret == LOADSTATE_HYPRLAND_UPDATED)\n            g_pPluginManager->notify(ICON_INFO, 0, 10000, \"[hyprpm] Enabled plugin, but Hyprland was updated. Please restart Hyprland.\");\n\n        if (ret != LOADSTATE_OK)\n            return 1;\n    } else if (command[0] == \"disable\") {\n        if (command.size() < 2) {\n            std::println(stderr, \"{}\", failureString(\"Not enough args for disable.\"));\n            return 1;\n        }\n\n        if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {\n            std::println(stderr, \"{}\", failureString(\"Couldn't disable plugin (missing?)\"));\n            return 1;\n        }\n\n        NSys::root::cacheSudo();\n        CScopeGuard x([] { NSys::root::dropSudo(); });\n\n        auto        ret = g_pPluginManager->ensurePluginsLoadState();\n\n        if (ret != LOADSTATE_OK)\n            return 1;\n    } else if (command[0] == \"reload\") {\n        auto ret = g_pPluginManager->ensurePluginsLoadState(force);\n\n        if (ret != LOADSTATE_OK) {\n            switch (ret) {\n                case LOADSTATE_FAIL:\n                case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, \"[hyprpm] Failed to load plugins\"); break;\n                case LOADSTATE_HEADERS_OUTDATED:\n                    g_pPluginManager->notify(ICON_ERROR, 0, 10000, \"[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually.\");\n                    break;\n                default: break;\n            }\n\n            return 1;\n        } else if (notify) {\n            g_pPluginManager->notify(ICON_OK, 0, 4000, \"[hyprpm] Loaded plugins\");\n        }\n    } else if (command[0] == \"purge-cache\") {\n        NSys::root::cacheSudo();\n        CScopeGuard x([] { NSys::root::dropSudo(); });\n        DataState::purgeAllCache();\n    } else if (command[0] == \"list\") {\n        g_pPluginManager->listAllPlugins();\n    } else {\n        std::println(stderr, \"{}\", HELP);\n        return 1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "hyprpm/src/progress/CProgressBar.cpp",
    "content": "#include \"CProgressBar.hpp\"\n\n#include <sys/ioctl.h>\n#include <unistd.h>\n#include <cmath>\n#include <format>\n#include <print>\n#include <cstdio>\n\n#include <algorithm>\n#include <sstream>\n#include <hyprutils/memory/Casts.hpp>\n#include \"../helpers/Colors.hpp\"\n\nusing namespace Hyprutils::Memory;\n\nstatic winsize getTerminalSize() {\n    winsize w{};\n    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);\n    return w;\n}\n\nstatic void clearCurrentLine() {\n    std::print(\"\\r\\33[2K\"); // ansi escape sequence to clear entire line\n}\n\nvoid CProgressBar::printMessageAbove(const std::string& msg) {\n    clearCurrentLine();\n    std::print(\"\\r{}\\n\", msg);\n\n    print(); // reprint bar underneath\n}\n\nvoid CProgressBar::print() {\n    const auto w = getTerminalSize();\n\n    if (m_bFirstPrint) {\n        std::print(\"\\n\");\n        m_bFirstPrint = false;\n    }\n\n    clearCurrentLine();\n\n    float percentDone = 0.0f;\n    if (m_fPercentage >= 0.0f)\n        percentDone = m_fPercentage;\n    else {\n        // check for divide-by-zero\n        percentDone = m_iMaxSteps > 0 ? sc<float>(m_iSteps) / m_iMaxSteps : 0.0f;\n    }\n    // clamp to ensure no overflows (sanity check)\n    percentDone = std::clamp(percentDone, 0.0f, 1.0f);\n\n    const size_t       BARWIDTH = std::clamp<size_t>(w.ws_col - m_szCurrentMessage.length() - 2, 0, 50);\n\n    std::ostringstream oss;\n    oss << ' ' << Colors::GREEN;\n\n    size_t filled = std::floor(percentDone * BARWIDTH);\n    size_t i      = 0;\n\n    for (; i < filled; ++i)\n        oss << \"━\";\n\n    if (i < BARWIDTH) {\n        oss << \"╍\" << Colors::RESET;\n        ++i;\n        for (; i < BARWIDTH; ++i)\n            oss << \"━\";\n    } else\n        oss << Colors::RESET;\n\n    if (m_fPercentage >= 0.0f)\n        oss << \"  \" << std::format(\"{}%\", sc<int>(percentDone * 100.0)) << ' ';\n    else\n        oss << \"  \" << std::format(\"{} / {}\", m_iSteps, m_iMaxSteps) << ' ';\n\n    std::print(\"{} {}\", oss.str(), m_szCurrentMessage);\n    std::fflush(stdout);\n}\n"
  },
  {
    "path": "hyprpm/src/progress/CProgressBar.hpp",
    "content": "#pragma once\n\n#include <string>\n\nclass CProgressBar {\n  public:\n    void        print();\n    void        printMessageAbove(const std::string& msg);\n\n    std::string m_szCurrentMessage = \"\";\n    size_t      m_iSteps           = 0;\n    size_t      m_iMaxSteps        = 0;\n    float       m_fPercentage      = -1; // if != -1, use percentage\n\n  private:\n    bool m_bFirstPrint = true;\n};\n"
  },
  {
    "path": "hyprtester/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.19)\n\nproject(hyprtester DESCRIPTION \"Hyprland test suite\")\n\ninclude(GNUInstallDirs)\n\nset(CMAKE_CXX_STANDARD 26)\nset(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)\n\nfind_package(PkgConfig REQUIRED)\n\npkg_check_modules(hyprtester_deps REQUIRED IMPORTED_TARGET hyprutils>=0.5.0)\n\nfile(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS \"src/*.cpp\")\n\nadd_executable(hyprtester ${SRCFILES})\nadd_custom_command(\n  TARGET hyprtester\n  POST_BUILD\n  COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/plugin/build.sh\n  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/plugin)\n\ntarget_link_libraries(hyprtester PUBLIC PkgConfig::hyprtester_deps)\n\ninstall(TARGETS hyprtester)\n\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.conf\n        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)\n\ninstall(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so\n        DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)\n\nfile(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/src/tests/clients/build.hpp\n  \"#include <string>\\n\"\n  \"static const std::string binaryDir = \\\"${CMAKE_CURRENT_BINARY_DIR}\\\";\"\n)\n\n######## wayland protocols testing stuff\n\nif(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)\n  set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)\nendif()\n\nfind_package(hyprwayland-scanner 0.4.0 REQUIRED)\npkg_check_modules(\n  protocols_deps\n  REQUIRED\n  IMPORTED_TARGET\n  hyprutils>=0.8.0\n  wayland-client\n  wayland-protocols\n)\n\npkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)\nmessage(STATUS \"Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}\")\npkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)\nmessage(STATUS \"Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}\")\n\n# gen core wayland stuff\nadd_custom_command(\n  OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.cpp\n         ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.hpp\n  COMMAND hyprwayland-scanner --wayland-enums --client\n          ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_CURRENT_SOURCE_DIR}/protocols/\n  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})\n\nfunction(protocolNew protoPath protoName external)\n  if(external)\n    set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})\n  else()\n    set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})\n  endif()\n\n  add_custom_command(\n    OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.cpp\n           ${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.hpp\n    COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml\n            ${CMAKE_CURRENT_SOURCE_DIR}/protocols/\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})\nendfunction()\nfunction(clientNew sourceName)\n  cmake_parse_arguments(PARSE_ARGV 1 ARG \"\" \"\" \"PROTOS\")\n\n  add_executable(${sourceName} clients/${sourceName}.cpp)\n\n  target_include_directories(${sourceName} BEFORE PRIVATE \"${CMAKE_CURRENT_SOURCE_DIR}/protocols\")\n  target_link_libraries(${sourceName} PUBLIC PkgConfig::protocols_deps)\n\n  target_sources(${sourceName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.hpp)\n\n  foreach(protoName IN LISTS ARG_PROTOS)\n    target_sources(${sourceName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.cpp\n                                         ${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.hpp)\n  endforeach()\nendfunction()\n\nprotocolnew(\"staging/pointer-warp\" \"pointer-warp-v1\" false)\nprotocolnew(\"stable/xdg-shell\" \"xdg-shell\" false)\nprotocolnew(\"unstable/keyboard-shortcuts-inhibit\" \"keyboard-shortcuts-inhibit-unstable-v1\" false)\n\nclientNew(\"pointer-warp\" PROTOS \"pointer-warp-v1\" \"xdg-shell\")\nclientNew(\"pointer-scroll\" PROTOS \"xdg-shell\")\nclientNew(\"child-window\" PROTOS \"xdg-shell\")\nclientNew(\"shortcut-inhibitor\" PROTOS \"xdg-shell\" \"keyboard-shortcuts-inhibit-unstable-v1\")\n"
  },
  {
    "path": "hyprtester/clients/child-window.cpp",
    "content": "#include <print>\n#include <poll.h>\n#include <sys/mman.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <unistd.h>\n\n#include <wayland-client.h>\n#include <wayland.hpp>\n#include <xdg-shell.hpp>\n\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n\nusing Hyprutils::Math::Vector2D;\nusing namespace Hyprutils::Memory;\n\nstruct SWlState {\n    wl_display*                  display;\n    CSharedPointer<CCWlRegistry> registry;\n\n    // protocols\n    CSharedPointer<CCWlCompositor> wlCompositor;\n    CSharedPointer<CCWlSeat>       wlSeat;\n    CSharedPointer<CCWlShm>        wlShm;\n    CSharedPointer<CCXdgWmBase>    xdgShell;\n\n    // shm/buffer stuff\n    CSharedPointer<CCWlShmPool> shmPool;\n    CSharedPointer<CCWlBuffer>  shmBuf;\n    CSharedPointer<CCWlBuffer>  shmBuf2;\n    int                         shmFd            = 0;\n    size_t                      shmBufSize       = 0;\n    bool                        xrgb8888_support = false;\n\n    // surface/toplevel stuff\n    CSharedPointer<CCWlSurface>   surf;\n    CSharedPointer<CCXdgSurface>  xdgSurf;\n    CSharedPointer<CCXdgToplevel> xdgToplevel;\n    Vector2D                      geom;\n\n    // pointer\n    CSharedPointer<CCWlPointer> pointer;\n    uint32_t                    enterSerial = 0;\n};\n\nbool debug, shouldExit, started;\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void clientLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::string text = std::vformat(fmt.get(), std::make_format_args(args...));\n    std::println(\"{}\", text);\n    std::fflush(stdout);\n}\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void debugLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::string text = std::vformat(fmt.get(), std::make_format_args(args...));\n    if (!debug)\n        return;\n    std::println(\"{}\", text);\n    std::fflush(stdout);\n}\n\nstatic bool bindRegistry(SWlState& state) {\n    state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));\n\n    state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {\n        const std::string NAME = name;\n        if (NAME == \"wl_compositor\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));\n        } else if (NAME == \"wl_shm\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));\n        } else if (NAME == \"wl_seat\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));\n        } else if (NAME == \"xdg_wm_base\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));\n        }\n    });\n    state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog(\"Global {} removed\", id); });\n\n    wl_display_roundtrip(state.display);\n\n    if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell) {\n        clientLog(\"Failed to get protocols from Hyprland\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool createShm(SWlState& state, Vector2D geom) {\n    if (!state.xrgb8888_support)\n        return false;\n\n    size_t stride = geom.x * 4;\n    size_t size   = geom.y * stride;\n    if (!state.shmPool) {\n        const char* name = \"/wl-shm-pointer-warp\";\n        state.shmFd      = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);\n        if (state.shmFd < 0)\n            return false;\n\n        if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size * 2) < 0) {\n            close(state.shmFd);\n            return false;\n        }\n\n        state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size * 2));\n        if (!state.shmPool->resource()) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n        state.shmBufSize = size;\n    } else if (size > state.shmBufSize) {\n        if (ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n\n        state.shmPool->sendResize(size * 2);\n        state.shmBufSize = size;\n    }\n\n    auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));\n    if (!buf->resource())\n        return false;\n\n    if (state.shmBuf) {\n        state.shmBuf->sendDestroy();\n        state.shmBuf.reset();\n    }\n    state.shmBuf = buf;\n\n    return true;\n}\n\nstatic bool setupToplevel(SWlState& state) {\n    state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {\n        if (format == WL_SHM_FORMAT_XRGB8888)\n            state.xrgb8888_support = true;\n    });\n\n    state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });\n\n    state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());\n    if (!state.surf->resource())\n        return false;\n\n    state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));\n    if (!state.xdgSurf->resource())\n        return false;\n\n    state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());\n    if (!state.xdgToplevel->resource())\n        return false;\n\n    state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });\n\n    state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {\n        state.geom = {1280, 720};\n\n        if (!createShm(state, state.geom))\n            exit(-1);\n    });\n\n    state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {\n        if (!state.shmBuf)\n            debugLog(\"xdgSurf configure but no buf made yet?\");\n\n        state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);\n        state.surf->sendAttach(state.shmBuf.get(), 0, 0);\n        state.surf->sendCommit();\n\n        state.xdgSurf->sendAckConfigure(serial);\n\n        if (!started) {\n            started = true;\n            clientLog(\"started\");\n        }\n    });\n\n    state.xdgToplevel->sendSetTitle(\"child-test parent\");\n    state.xdgToplevel->sendSetAppId(\"child-test-parent\");\n\n    state.surf->sendAttach(nullptr, 0, 0);\n    state.surf->sendCommit();\n\n    return true;\n}\n\nstatic bool setupSeat(SWlState& state) {\n    state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());\n    if (!state.pointer->resource())\n        return false;\n\n    state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {\n        debugLog(\"Got pointer enter event, serial {}, x {}, y {}\", serial, x, y);\n        state.enterSerial = serial;\n    });\n\n    state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog(\"Got pointer leave event, serial {}\", serial); });\n\n    state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog(\"Got pointer motion event, serial {}, x {}, y {}\", serial, x, y); });\n\n    return true;\n}\n\nstruct SChildWindow {\n    CSharedPointer<CCWlSurface>   surface;\n    CSharedPointer<CCXdgSurface>  xSurface;\n    CSharedPointer<CCXdgToplevel> toplevel;\n};\n\nstatic void parseRequest(SWlState& state, std::string str, SChildWindow& window) {\n    if (str.starts_with(\"exit\")) {\n        shouldExit = true;\n        return;\n    }\n\n    size_t index = str.find_first_of('\\n');\n    str          = str.substr(0, index);\n\n    if (str == \"toplevel\") {\n        window.surface  = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());\n        window.xSurface = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(window.surface->resource()));\n\n        window.xSurface->setConfigure([&](CCXdgSurface* p, uint32_t serial) {\n            if (!state.shmBuf)\n                debugLog(\"xdgSurf configure but no buf made yet?\");\n\n            window.xSurface->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);\n            window.surface->sendAttach(state.shmBuf2.get(), 0, 0);\n            window.surface->sendCommit();\n\n            window.xSurface->sendAckConfigure(serial);\n        });\n\n        window.toplevel = makeShared<CCXdgToplevel>(window.xSurface->sendGetToplevel());\n\n        window.toplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {\n            size_t stride = 1280 * 4;\n            size_t size   = 720 * stride;\n\n            auto   buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(size, state.geom.x, state.geom.y, stride, WL_SHM_FORMAT_XRGB8888));\n            if (!buf->resource())\n                clientLog(\"Failed to create child buffer\");\n\n            if (state.shmBuf2) {\n                state.shmBuf2->sendDestroy();\n                state.shmBuf2.reset();\n            }\n            state.shmBuf2 = buf;\n        });\n\n        window.toplevel->sendSetTitle(\"child-test child\");\n        window.toplevel->sendSetAppId(\"child-test-child\");\n        window.toplevel->sendSetParent(state.xdgToplevel.get());\n\n        window.surface->sendAttach(nullptr, 0, 0);\n        window.surface->sendCommit();\n        clientLog(\"child started\");\n        return;\n    }\n}\n\nint main(int argc, char** argv) {\n    if (argc != 1 && argc != 2)\n        clientLog(\"Only the \\\"--debug\\\" switch is allowed, it turns on debug logs.\");\n\n    if (argc == 2 && std::string{argv[1]} == \"--debug\")\n        debug = true;\n\n    SWlState     state;\n    SChildWindow window;\n\n    // WAYLAND_DISPLAY env should be set to the correct one\n    state.display = wl_display_connect(nullptr);\n    if (!state.display) {\n        clientLog(\"Failed to connect to wayland display\");\n        return -1;\n    }\n\n    if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))\n        return -1;\n\n    std::array<char, 1024> readBuf;\n    readBuf.fill(0);\n\n    wl_display_flush(state.display);\n\n    struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};\n    while (!shouldExit && poll(fds, 2, 0) != -1) {\n        if (fds[0].revents & POLLIN) {\n            wl_display_flush(state.display);\n\n            if (wl_display_prepare_read(state.display) == 0) {\n                wl_display_read_events(state.display);\n                wl_display_dispatch_pending(state.display);\n            } else\n                wl_display_dispatch(state.display);\n\n            int ret = 0;\n            do {\n                ret = wl_display_dispatch_pending(state.display);\n                wl_display_flush(state.display);\n            } while (ret > 0);\n        }\n\n        if (fds[1].revents & POLLIN) {\n            ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);\n            if (bytesRead == -1)\n                continue;\n            readBuf[bytesRead] = 0;\n\n            parseRequest(state, std::string{readBuf.data()}, window);\n        }\n    }\n\n    wl_display* display = state.display;\n    state               = {};\n    window              = {};\n\n    wl_display_disconnect(display);\n    return 0;\n}\n"
  },
  {
    "path": "hyprtester/clients/pointer-scroll.cpp",
    "content": "#include <cstring>\n#include <sys/poll.h>\n#include <sys/mman.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <print>\n#include <format>\n#include <string>\n#include <fstream>\n\n#include <wayland-client.h>\n#include <wayland.hpp>\n#include <xdg-shell.hpp>\n#include <pointer-warp-v1.hpp>\n\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n\nusing Hyprutils::Math::Vector2D;\nusing namespace Hyprutils::Memory;\n\nstruct SWlState {\n    wl_display*                  display;\n    CSharedPointer<CCWlRegistry> registry;\n\n    // protocols\n    CSharedPointer<CCWlCompositor> wlCompositor;\n    CSharedPointer<CCWlSeat>       wlSeat;\n    CSharedPointer<CCWlShm>        wlShm;\n    CSharedPointer<CCXdgWmBase>    xdgShell;\n\n    // shm/buffer stuff\n    CSharedPointer<CCWlShmPool> shmPool;\n    CSharedPointer<CCWlBuffer>  shmBuf;\n    int                         shmFd;\n    size_t                      shmBufSize;\n    bool                        xrgb8888_support = false;\n\n    // surface/toplevel stuff\n    CSharedPointer<CCWlSurface>   surf;\n    CSharedPointer<CCXdgSurface>  xdgSurf;\n    CSharedPointer<CCXdgToplevel> xdgToplevel;\n    Vector2D                      geom;\n\n    // pointer\n    CSharedPointer<CCWlPointer> pointer;\n    uint32_t                    enterSerial;\n\n    // last delta\n    float lastScrollDelta = -1.F;\n    bool  writeDelta      = false;\n};\n\nstatic std::ofstream logfile;\n\nstatic bool          debug, started, shouldExit;\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void clientLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::string text = std::vformat(fmt.get(), std::make_format_args(args...));\n    std::println(\"{}\", text);\n    logfile << text << std::endl;\n    std::fflush(stdout);\n}\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void debugLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::string text = std::vformat(fmt.get(), std::make_format_args(args...));\n    logfile << text << std::endl;\n    if (!debug)\n        return;\n    std::println(\"{}\", text);\n    std::fflush(stdout);\n}\n\nstatic bool bindRegistry(SWlState& state) {\n    state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));\n\n    state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {\n        const std::string NAME = name;\n        if (NAME == \"wl_compositor\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));\n        } else if (NAME == \"wl_shm\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));\n        } else if (NAME == \"wl_seat\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));\n        } else if (NAME == \"xdg_wm_base\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));\n        }\n    });\n    state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog(\"Global {} removed\", id); });\n\n    wl_display_roundtrip(state.display);\n\n    if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell) {\n        clientLog(\"Failed to get protocols from Hyprland\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool createShm(SWlState& state, Vector2D geom) {\n    if (!state.xrgb8888_support)\n        return false;\n\n    size_t stride = geom.x * 4;\n    size_t size   = geom.y * stride;\n    if (!state.shmPool) {\n        const char* name = \"/wl-shm-pointer-scroll\";\n        state.shmFd      = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);\n        if (state.shmFd < 0)\n            return false;\n\n        if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            return false;\n        }\n\n        state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size));\n        if (!state.shmPool->resource()) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n        state.shmBufSize = size;\n    } else if (size > state.shmBufSize) {\n        if (ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n\n        state.shmPool->sendResize(size);\n        state.shmBufSize = size;\n    }\n\n    auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));\n    if (!buf->resource())\n        return false;\n\n    if (state.shmBuf) {\n        state.shmBuf->sendDestroy();\n        state.shmBuf.reset();\n    }\n\n    state.shmBuf = buf;\n\n    return true;\n}\n\nstatic bool setupToplevel(SWlState& state) {\n    state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {\n        if (format == WL_SHM_FORMAT_XRGB8888)\n            state.xrgb8888_support = true;\n    });\n\n    state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });\n\n    state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());\n    if (!state.surf->resource())\n        return false;\n\n    state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));\n    if (!state.xdgSurf->resource())\n        return false;\n\n    state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());\n    if (!state.xdgToplevel->resource())\n        return false;\n\n    state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });\n\n    state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {\n        state.geom = {1280, 720};\n\n        if (!createShm(state, state.geom))\n            exit(-1);\n    });\n\n    state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {\n        if (!state.shmBuf)\n            debugLog(\"xdgSurf configure but no buf made yet?\");\n\n        state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);\n        state.surf->sendAttach(state.shmBuf.get(), 0, 0);\n        state.surf->sendCommit();\n\n        state.xdgSurf->sendAckConfigure(serial);\n\n        if (!started) {\n            started = true;\n            clientLog(\"started\");\n        }\n    });\n\n    state.xdgToplevel->sendSetTitle(\"pointer-scroll test client\");\n    state.xdgToplevel->sendSetAppId(\"pointer-scroll\");\n\n    state.surf->sendAttach(nullptr, 0, 0);\n    state.surf->sendCommit();\n\n    return true;\n}\n\nstatic bool setupSeat(SWlState& state) {\n    state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());\n    if (!state.pointer->resource())\n        return false;\n\n    state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {\n        debugLog(\"Got pointer enter event, serial {}, x {}, y {}\", serial, x, y);\n        state.enterSerial = serial;\n    });\n\n    state.pointer->setAxis([&](CCWlPointer* p, uint32_t time, wl_pointer_axis axis, wl_fixed_t delta) {\n        debugLog(\"axis: ax {} delta {}\", (int)axis, wl_fixed_to_double(delta));\n\n        if (state.writeDelta) {\n            clientLog(\"{:.2f}\", wl_fixed_to_double(delta));\n            state.writeDelta      = false;\n            state.lastScrollDelta = -1;\n            return;\n        }\n\n        state.lastScrollDelta = wl_fixed_to_double(delta);\n        state.writeDelta      = true;\n    });\n\n    state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog(\"Got pointer leave event, serial {}\", serial); });\n\n    state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog(\"Got pointer motion event, serial {}, x {}, y {}\", serial, x, y); });\n\n    return true;\n}\n\n// return last delta after axis\nstatic void parseRequest(SWlState& state, std::string req) {\n    if (!state.writeDelta) {\n        state.writeDelta = true;\n        return;\n    }\n\n    clientLog(\"{:.2f}\", state.lastScrollDelta);\n    state.writeDelta      = false;\n    state.lastScrollDelta = -1;\n}\n\nint main(int argc, char** argv) {\n    logfile.open(\"pointer-scroll.txt\", std::ios::trunc);\n\n    if (argc != 1 && argc != 2)\n        clientLog(\"Only the \\\"--debug\\\" switch is allowed, it turns on debug logs.\");\n\n    if (argc == 2 && std::string{argv[1]} == \"--debug\")\n        debug = true;\n\n    SWlState state;\n\n    // WAYLAND_DISPLAY env should be set to the correct one\n    state.display = wl_display_connect(nullptr);\n    if (!state.display) {\n        clientLog(\"Failed to connect to wayland display\");\n        return -1;\n    }\n\n    if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))\n        return -1;\n\n    std::array<char, 1024> readBuf;\n    readBuf.fill(0);\n\n    wl_display_flush(state.display);\n\n    struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};\n    while (!shouldExit && poll(fds, 2, 0) != -1) {\n        if (fds[0].revents & POLLIN) {\n            wl_display_flush(state.display);\n\n            if (wl_display_prepare_read(state.display) == 0) {\n                wl_display_read_events(state.display);\n                wl_display_dispatch_pending(state.display);\n            } else\n                wl_display_dispatch(state.display);\n\n            int ret = 0;\n            do {\n                ret = wl_display_dispatch_pending(state.display);\n                wl_display_flush(state.display);\n            } while (ret > 0);\n        }\n\n        if (fds[1].revents & POLLIN) {\n            ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);\n            if (bytesRead == -1)\n                continue;\n            readBuf[bytesRead] = 0;\n\n            parseRequest(state, std::string{readBuf.data()});\n        }\n    }\n\n    wl_display* display = state.display;\n    state               = {};\n\n    wl_display_disconnect(display);\n    logfile.flush();\n    logfile.close();\n    return 0;\n}\n"
  },
  {
    "path": "hyprtester/clients/pointer-warp.cpp",
    "content": "#include <cstring>\n#include <sys/poll.h>\n#include <sys/mman.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <print>\n#include <format>\n#include <string>\n\n#include <wayland-client.h>\n#include <wayland.hpp>\n#include <xdg-shell.hpp>\n#include <pointer-warp-v1.hpp>\n\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n\nusing Hyprutils::Math::Vector2D;\nusing namespace Hyprutils::Memory;\n\nstruct SWlState {\n    wl_display*                  display;\n    CSharedPointer<CCWlRegistry> registry;\n\n    // protocols\n    CSharedPointer<CCWlCompositor>    wlCompositor;\n    CSharedPointer<CCWlSeat>          wlSeat;\n    CSharedPointer<CCWlShm>           wlShm;\n    CSharedPointer<CCXdgWmBase>       xdgShell;\n    CSharedPointer<CCWpPointerWarpV1> pointerWarp;\n\n    // shm/buffer stuff\n    CSharedPointer<CCWlShmPool> shmPool;\n    CSharedPointer<CCWlBuffer>  shmBuf;\n    int                         shmFd;\n    size_t                      shmBufSize;\n    bool                        xrgb8888_support = false;\n\n    // surface/toplevel stuff\n    CSharedPointer<CCWlSurface>   surf;\n    CSharedPointer<CCXdgSurface>  xdgSurf;\n    CSharedPointer<CCXdgToplevel> xdgToplevel;\n    Vector2D                      geom;\n\n    // pointer\n    CSharedPointer<CCWlPointer> pointer;\n    uint32_t                    enterSerial;\n};\n\nstatic bool debug, started, shouldExit;\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void clientLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::println(\"{}\", std::vformat(fmt.get(), std::make_format_args(args...)));\n    std::fflush(stdout);\n}\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void debugLog(std::format_string<Args...> fmt, Args&&... args) {\n    if (!debug)\n        return;\n    std::println(\"{}\", std::vformat(fmt.get(), std::make_format_args(args...)));\n    std::fflush(stdout);\n}\n\nstatic bool bindRegistry(SWlState& state) {\n    state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));\n\n    state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {\n        const std::string NAME = name;\n        if (NAME == \"wl_compositor\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));\n        } else if (NAME == \"wl_shm\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));\n        } else if (NAME == \"wl_seat\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));\n        } else if (NAME == \"xdg_wm_base\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));\n        } else if (NAME == \"wp_pointer_warp_v1\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.pointerWarp = makeShared<CCWpPointerWarpV1>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wp_pointer_warp_v1_interface, 1));\n        }\n    });\n    state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog(\"Global {} removed\", id); });\n\n    wl_display_roundtrip(state.display);\n\n    if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.pointerWarp) {\n        clientLog(\"Failed to get protocols from Hyprland\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool createShm(SWlState& state, Vector2D geom) {\n    if (!state.xrgb8888_support)\n        return false;\n\n    size_t stride = geom.x * 4;\n    size_t size   = geom.y * stride;\n    if (!state.shmPool) {\n        const char* name = \"/wl-shm-pointer-warp\";\n        state.shmFd      = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);\n        if (state.shmFd < 0)\n            return false;\n\n        if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            return false;\n        }\n\n        state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size));\n        if (!state.shmPool->resource()) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n        state.shmBufSize = size;\n    } else if (size > state.shmBufSize) {\n        if (ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n\n        state.shmPool->sendResize(size);\n        state.shmBufSize = size;\n    }\n\n    auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));\n    if (!buf->resource())\n        return false;\n\n    if (state.shmBuf) {\n        state.shmBuf->sendDestroy();\n        state.shmBuf.reset();\n    }\n\n    state.shmBuf = buf;\n\n    return true;\n}\n\nstatic bool setupToplevel(SWlState& state) {\n    state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {\n        if (format == WL_SHM_FORMAT_XRGB8888)\n            state.xrgb8888_support = true;\n    });\n\n    state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });\n\n    state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());\n    if (!state.surf->resource())\n        return false;\n\n    state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));\n    if (!state.xdgSurf->resource())\n        return false;\n\n    state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());\n    if (!state.xdgToplevel->resource())\n        return false;\n\n    state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });\n\n    state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {\n        state.geom = {1280, 720};\n\n        if (!createShm(state, state.geom))\n            exit(-1);\n    });\n\n    state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {\n        if (!state.shmBuf)\n            debugLog(\"xdgSurf configure but no buf made yet?\");\n\n        state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);\n        state.surf->sendAttach(state.shmBuf.get(), 0, 0);\n        state.surf->sendCommit();\n\n        state.xdgSurf->sendAckConfigure(serial);\n\n        if (!started) {\n            started = true;\n            clientLog(\"started\");\n        }\n    });\n\n    state.xdgToplevel->sendSetTitle(\"pointer-warp test client\");\n    state.xdgToplevel->sendSetAppId(\"pointer-warp\");\n\n    state.surf->sendAttach(nullptr, 0, 0);\n    state.surf->sendCommit();\n\n    return true;\n}\n\nstatic bool setupSeat(SWlState& state) {\n    state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());\n    if (!state.pointer->resource())\n        return false;\n\n    state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {\n        debugLog(\"Got pointer enter event, serial {}, x {}, y {}\", serial, x, y);\n        state.enterSerial = serial;\n    });\n\n    state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog(\"Got pointer leave event, serial {}\", serial); });\n\n    state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog(\"Got pointer motion event, serial {}, x {}, y {}\", serial, x, y); });\n\n    return true;\n}\n\n// format is like below\n// \"warp 20 20\\n\" would ask to warp cursor to x=20,y=20 in surface local coords\nstatic void parseRequest(SWlState& state, std::string req) {\n    if (req.contains(\"exit\")) {\n        shouldExit = true;\n        return;\n    }\n\n    if (!req.starts_with(\"warp \"))\n        return;\n\n    auto it = req.find_first_of('\\n');\n    if (it == std::string::npos)\n        return;\n\n    req = req.substr(0, it);\n\n    it = req.find_first_of(' ');\n    if (it == std::string::npos)\n        return;\n\n    req = req.substr(it + 1);\n\n    it = req.find_first_of(' ');\n\n    int x = std::stoi(req.substr(0, it));\n    int y = std::stoi(req.substr(it + 1));\n\n    state.pointerWarp->sendWarpPointer(state.surf->resource(), state.pointer->resource(), wl_fixed_from_int(x), wl_fixed_from_int(y), state.enterSerial);\n\n    // sync the request then reply\n    wl_display_roundtrip(state.display);\n\n    clientLog(\"parsed request to move to x:{}, y:{}\", x, y);\n}\n\nint main(int argc, char** argv) {\n    if (argc != 1 && argc != 2)\n        clientLog(\"Only the \\\"--debug\\\" switch is allowed, it turns on debug logs.\");\n\n    if (argc == 2 && std::string{argv[1]} == \"--debug\")\n        debug = true;\n\n    SWlState state;\n\n    // WAYLAND_DISPLAY env should be set to the correct one\n    state.display = wl_display_connect(nullptr);\n    if (!state.display) {\n        clientLog(\"Failed to connect to wayland display\");\n        return -1;\n    }\n\n    if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))\n        return -1;\n\n    std::array<char, 1024> readBuf;\n    readBuf.fill(0);\n\n    wl_display_flush(state.display);\n\n    struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};\n    while (!shouldExit && poll(fds, 2, 0) != -1) {\n        if (fds[0].revents & POLLIN) {\n            wl_display_flush(state.display);\n\n            if (wl_display_prepare_read(state.display) == 0) {\n                wl_display_read_events(state.display);\n                wl_display_dispatch_pending(state.display);\n            } else\n                wl_display_dispatch(state.display);\n\n            int ret = 0;\n            do {\n                ret = wl_display_dispatch_pending(state.display);\n                wl_display_flush(state.display);\n            } while (ret > 0);\n        }\n\n        if (fds[1].revents & POLLIN) {\n            ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);\n            if (bytesRead == -1)\n                continue;\n            readBuf[bytesRead] = 0;\n\n            parseRequest(state, std::string{readBuf.data()});\n        }\n    }\n\n    wl_display* display = state.display;\n    state               = {};\n\n    wl_display_disconnect(display);\n    return 0;\n}\n"
  },
  {
    "path": "hyprtester/clients/shortcut-inhibitor.cpp",
    "content": "#include <sys/poll.h>\n#include <sys/mman.h>\n#include <fcntl.h>\n#include <print>\n\n#include <wayland-client.h>\n#include <wayland.hpp>\n#include <xdg-shell.hpp>\n#include <keyboard-shortcuts-inhibit-unstable-v1.hpp>\n\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n\nusing Hyprutils::Math::Vector2D;\nusing namespace Hyprutils::Memory;\n\nstruct SWlState {\n    wl_display*                  display;\n    CSharedPointer<CCWlRegistry> registry;\n\n    // protocols\n    CSharedPointer<CCWlCompositor>                         wlCompositor;\n    CSharedPointer<CCWlSeat>                               wlSeat;\n    CSharedPointer<CCWlShm>                                wlShm;\n    CSharedPointer<CCXdgWmBase>                            xdgShell;\n    CSharedPointer<CCZwpKeyboardShortcutsInhibitManagerV1> inhibitManager;\n\n    // shm/buffer stuff\n    CSharedPointer<CCWlShmPool> shmPool;\n    CSharedPointer<CCWlBuffer>  shmBuf;\n    int                         shmFd;\n    size_t                      shmBufSize;\n    bool                        xrgb8888_support = false;\n\n    // surface/toplevel stuff\n    CSharedPointer<CCWlSurface>   surf;\n    CSharedPointer<CCXdgSurface>  xdgSurf;\n    CSharedPointer<CCXdgToplevel> xdgToplevel;\n    Vector2D                      geom;\n\n    // pointer\n    CSharedPointer<CCWlPointer> pointer;\n    uint32_t                    enterSerial;\n\n    // shortcut inhibiting\n    CSharedPointer<CCZwpKeyboardShortcutsInhibitorV1> inhibitor;\n};\n\nstatic bool debug, started, shouldExit;\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void clientLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::string text = std::vformat(fmt.get(), std::make_format_args(args...));\n    std::println(\"{}\", text);\n    std::fflush(stdout);\n}\n\ntemplate <typename... Args>\n//NOLINTNEXTLINE\nstatic void debugLog(std::format_string<Args...> fmt, Args&&... args) {\n    std::string text = std::vformat(fmt.get(), std::make_format_args(args...));\n    if (!debug)\n        return;\n    std::println(\"{}\", text);\n    std::fflush(stdout);\n}\n\nstatic bool bindRegistry(SWlState& state) {\n    state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));\n\n    state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {\n        const std::string NAME = name;\n        if (NAME == \"wl_compositor\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));\n        } else if (NAME == \"wl_shm\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));\n        } else if (NAME == \"wl_seat\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));\n        } else if (NAME == \"xdg_wm_base\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));\n        } else if (NAME == \"zwp_keyboard_shortcuts_inhibit_manager_v1\") {\n            debugLog(\"  > binding to global: {} (version {}) with id {}\", name, version, id);\n            state.inhibitManager = makeShared<CCZwpKeyboardShortcutsInhibitManagerV1>(\n                (wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1));\n        }\n    });\n    state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog(\"Global {} removed\", id); });\n\n    wl_display_roundtrip(state.display);\n\n    if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.inhibitManager) {\n        clientLog(\"Failed to get protocols from Hyprland\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool createShm(SWlState& state, Vector2D geom) {\n    if (!state.xrgb8888_support)\n        return false;\n\n    size_t stride = geom.x * 4;\n    size_t size   = geom.y * stride;\n    if (!state.shmPool) {\n        const char* name = \"/wl-shm-shortcut-inhibitor\";\n        state.shmFd      = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);\n        if (state.shmFd < 0)\n            return false;\n\n        if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            return false;\n        }\n\n        state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size));\n        if (!state.shmPool->resource()) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n        state.shmBufSize = size;\n    } else if (size > state.shmBufSize) {\n        if (ftruncate(state.shmFd, size) < 0) {\n            close(state.shmFd);\n            state.shmFd = -1;\n            state.shmPool.reset();\n            return false;\n        }\n\n        state.shmPool->sendResize(size);\n        state.shmBufSize = size;\n    }\n\n    auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));\n    if (!buf->resource())\n        return false;\n\n    if (state.shmBuf) {\n        state.shmBuf->sendDestroy();\n        state.shmBuf.reset();\n    }\n\n    state.shmBuf = buf;\n\n    return true;\n}\n\nstatic bool setupToplevel(SWlState& state) {\n    state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {\n        if (format == WL_SHM_FORMAT_XRGB8888)\n            state.xrgb8888_support = true;\n    });\n\n    state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });\n\n    state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());\n    if (!state.surf->resource())\n        return false;\n\n    state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));\n    if (!state.xdgSurf->resource())\n        return false;\n\n    state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());\n    if (!state.xdgToplevel->resource())\n        return false;\n\n    state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });\n\n    state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {\n        state.geom = {1280, 720};\n\n        if (!createShm(state, state.geom))\n            exit(-1);\n    });\n\n    state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {\n        if (!state.shmBuf)\n            debugLog(\"xdgSurf configure but no buf made yet?\");\n\n        state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);\n        state.surf->sendAttach(state.shmBuf.get(), 0, 0);\n        state.surf->sendCommit();\n\n        state.xdgSurf->sendAckConfigure(serial);\n\n        if (!started) {\n            started = true;\n            clientLog(\"started\");\n        }\n    });\n\n    state.xdgToplevel->sendSetTitle(\"shortcut-inhibitor test client\");\n    state.xdgToplevel->sendSetAppId(\"shortcut-inhibitor\");\n\n    state.surf->sendAttach(nullptr, 0, 0);\n    state.surf->sendCommit();\n\n    return true;\n}\n\nstatic bool setupSeat(SWlState& state) {\n    state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());\n    if (!state.pointer->resource())\n        return false;\n\n    state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {\n        debugLog(\"Got pointer enter event, serial {}, x {}, y {}\", serial, x, y);\n        state.enterSerial = serial;\n    });\n\n    state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog(\"Got pointer leave event, serial {}\", serial); });\n\n    state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog(\"Got pointer motion event, serial {}, x {}, y {}\", serial, x, y); });\n\n    return true;\n}\n\nstatic void parseRequest(SWlState& state, std::string req) {\n    if (req.starts_with(\"on\")) {\n        state.inhibitor = makeShared<CCZwpKeyboardShortcutsInhibitorV1>(state.inhibitManager->sendInhibitShortcuts(state.surf->resource(), state.wlSeat->resource()));\n\n        state.inhibitor->setActive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog(\"inhibiting\"); });\n        state.inhibitor->setInactive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog(\"inhibit disabled by compositor\"); });\n    } else if (req.starts_with(\"off\")) {\n        state.inhibitor->sendDestroy();\n        state.inhibitor.reset();\n        shouldExit = true;\n        clientLog(\"inhibit disabled by request\");\n    }\n}\n\nint main(int argc, char** argv) {\n    if (argc != 1 && argc != 2)\n        clientLog(\"Only the \\\"--debug\\\" switch is allowed, it turns on debug logs.\");\n\n    if (argc == 2 && std::string{argv[1]} == \"--debug\")\n        debug = true;\n\n    SWlState state;\n\n    // WAYLAND_DISPLAY env should be set to the correct one\n    state.display = wl_display_connect(nullptr);\n    if (!state.display) {\n        clientLog(\"Failed to connect to wayland display\");\n        return -1;\n    }\n\n    if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))\n        return -1;\n\n    std::array<char, 1024> readBuf;\n    readBuf.fill(0);\n\n    wl_display_flush(state.display);\n\n    struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};\n    while (!shouldExit && poll(fds, 2, 0) != -1) {\n        if (fds[0].revents & POLLIN) {\n            wl_display_flush(state.display);\n\n            if (wl_display_prepare_read(state.display) == 0) {\n                wl_display_read_events(state.display);\n                wl_display_dispatch_pending(state.display);\n            } else\n                wl_display_dispatch(state.display);\n\n            int ret = 0;\n            do {\n                ret = wl_display_dispatch_pending(state.display);\n                wl_display_flush(state.display);\n            } while (ret > 0);\n        }\n\n        if (fds[1].revents & POLLIN) {\n            ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);\n            if (bytesRead == -1)\n                continue;\n            readBuf[bytesRead] = 0;\n\n            parseRequest(state, std::string{readBuf.data()});\n        }\n    }\n\n    wl_display* display = state.display;\n    state               = {};\n\n    wl_display_disconnect(display);\n    return 0;\n}\n"
  },
  {
    "path": "hyprtester/plugin/Makefile",
    "content": "CXXFLAGS = -shared -fPIC -g -std=c++2b -Wno-c++11-narrowing\nINCLUDES = `pkg-config --cflags pixman-1 libdrm pangocairo libinput libudev wayland-server xkbcommon`\nLIBS = `pkg-config --libs pangocairo`\n\nSRC = src/main.cpp\nTARGET = hyprtestplugin.so\n\nall: $(TARGET)\n\n$(TARGET): $(SRC)\n\t$(CXX) $(CXXFLAGS) -I../.. -I../../protocols $(INCLUDES) $^ $> -o $@ $(LIBS) -O2\n\nclean:\n\trm -f ./$(TARGET)\n\n.PHONY: all clean\n"
  },
  {
    "path": "hyprtester/plugin/build.sh",
    "content": "#!/bin/sh\n\nmake clean\nmake all\n"
  },
  {
    "path": "hyprtester/plugin/src/globals.hpp",
    "content": "#pragma once\n\n#include <src/plugins/PluginAPI.hpp>\n\ninline HANDLE PHANDLE = nullptr;"
  },
  {
    "path": "hyprtester/plugin/src/main.cpp",
    "content": "#include <unistd.h>\n#include <src/includes.hpp>\n#include <sstream>\n#include <any>\n\n#define private public\n#include <src/config/ConfigManager.hpp>\n#include <src/config/ConfigDescriptions.hpp>\n#include <src/managers/input/InputManager.hpp>\n#include <src/managers/PointerManager.hpp>\n#include <src/managers/input/trackpad/TrackpadGestures.hpp>\n#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp>\n#include <src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp>\n#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>\n#include <src/desktop/view/LayerSurface.hpp>\n#include <src/Compositor.hpp>\n#include <src/desktop/state/FocusState.hpp>\n#include <src/layout/LayoutManager.hpp>\n#undef private\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n#include <hyprutils/string/VarList.hpp>\nusing namespace Hyprutils::Utils;\nusing namespace Hyprutils::String;\n\n#include \"globals.hpp\"\n\n// Do NOT change this function.\nAPICALL EXPORT std::string PLUGIN_API_VERSION() {\n    return HYPRLAND_API_VERSION;\n}\n\nstatic SDispatchResult test(std::string in) {\n    bool        success = true;\n    std::string errors  = \"\";\n\n    if (g_pConfigManager->m_configValueNumber != CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) {\n        errors += \"config value number mismatches descriptions size\\n\";\n        success = false;\n    }\n\n    return SDispatchResult{\n        .success = success,\n        .error   = errors,\n    };\n}\n\n// Trigger a snap move event for the active window\nstatic SDispatchResult snapMove(std::string in) {\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n    if (!PLASTWINDOW->m_isFloating)\n        return {.success = false, .error = \"Window must be floating\"};\n\n    Vector2D pos  = PLASTWINDOW->m_realPosition->goal();\n    Vector2D size = PLASTWINDOW->m_realSize->goal();\n\n    g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size);\n\n    PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size});\n\n    return {};\n}\n\nclass CTestKeyboard : public IKeyboard {\n  public:\n    static SP<CTestKeyboard> create(bool isVirtual) {\n        auto keeb           = SP<CTestKeyboard>(new CTestKeyboard());\n        keeb->m_self        = keeb;\n        keeb->m_isVirtual   = isVirtual;\n        keeb->m_shareStates = !isVirtual;\n        keeb->m_hlName      = \"test-keyboard\";\n        keeb->m_deviceName  = \"test-keyboard\";\n        return keeb;\n    }\n\n    virtual bool isVirtual() {\n        return m_isVirtual;\n    }\n\n    virtual SP<Aquamarine::IKeyboard> aq() {\n        return nullptr;\n    }\n\n    void sendKey(uint32_t key, bool pressed) {\n        auto event = IKeyboard::SKeyEvent{\n            .timeMs  = sc<uint32_t>(Time::millis(Time::steadyNow())),\n            .keycode = key,\n            .state   = pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED,\n        };\n        updatePressed(event.keycode, pressed);\n        m_keyboardEvents.key.emit(event);\n    }\n\n    void destroy() {\n        m_events.destroy.emit();\n    }\n\n  private:\n    bool m_isVirtual = false;\n};\n\nclass CTestMouse : public IPointer {\n  public:\n    static SP<CTestMouse> create(bool isVirtual) {\n        auto maus          = SP<CTestMouse>(new CTestMouse());\n        maus->m_self       = maus;\n        maus->m_isVirtual  = isVirtual;\n        maus->m_deviceName = \"test-mouse\";\n        maus->m_hlName     = \"test-mouse\";\n        return maus;\n    }\n\n    virtual bool isVirtual() {\n        return m_isVirtual;\n    }\n\n    virtual SP<Aquamarine::IPointer> aq() {\n        return nullptr;\n    }\n\n    void destroy() {\n        m_events.destroy.emit();\n    }\n\n  private:\n    bool m_isVirtual = false;\n};\n\nSP<CTestMouse>         g_mouse;\nSP<CTestKeyboard>      g_keyboard;\n\nstatic SDispatchResult pressAlt(std::string in) {\n    g_pInputManager->m_lastMods = in == \"1\" ? HL_MODIFIER_ALT : 0;\n\n    return {.success = true};\n}\n\nstatic SDispatchResult simulateGesture(std::string in) {\n    CVarList data(in);\n\n    uint32_t fingers = 3;\n    try {\n        fingers = std::stoul(data[1]);\n    } catch (...) { return {.success = false}; }\n\n    if (data[0] == \"down\") {\n        g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});\n        g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {0, 300}});\n        g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});\n    } else if (data[0] == \"up\") {\n        g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});\n        g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {0, -300}});\n        g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});\n    } else if (data[0] == \"left\") {\n        g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});\n        g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {-300, 0}});\n        g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});\n    } else {\n        g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});\n        g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {300, 0}});\n        g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});\n    }\n\n    return {.success = true};\n}\n\nstatic SDispatchResult vkb(std::string in) {\n    auto tkb0 = CTestKeyboard::create(false);\n    auto tkb1 = CTestKeyboard::create(false);\n    auto vkb0 = CTestKeyboard::create(true);\n\n    g_pInputManager->newKeyboard(tkb0);\n    g_pInputManager->newKeyboard(tkb1);\n    g_pInputManager->newKeyboard(vkb0);\n\n    CScopeGuard    x([&] {\n        tkb0->destroy();\n        tkb1->destroy();\n        vkb0->destroy();\n    });\n\n    const auto&    PRESSED = g_pInputManager->getKeysFromAllKBs();\n    const uint32_t TESTKEY = 1;\n\n    tkb0->sendKey(TESTKEY, true);\n    if (!std::ranges::contains(PRESSED, TESTKEY)) {\n        return {\n            .success = false,\n            .error   = \"Expected pressed key not found\",\n        };\n    }\n\n    tkb1->sendKey(TESTKEY, true);\n    tkb0->sendKey(TESTKEY, false);\n    if (!std::ranges::contains(PRESSED, TESTKEY)) {\n        return {\n            .success = false,\n            .error   = \"Expected pressed key not found (kb share state)\",\n        };\n    }\n\n    vkb0->sendKey(TESTKEY, true);\n    tkb1->sendKey(TESTKEY, false);\n    if (std::ranges::contains(PRESSED, TESTKEY)) {\n        return {\n            .success = false,\n            .error   = \"Expected released key found in pressed (vkb no share state)\",\n        };\n    }\n\n    return {};\n}\n\nstatic SDispatchResult scroll(std::string in) {\n    double by;\n    try {\n        by = std::stod(in);\n    } catch (...) { return SDispatchResult{.success = false, .error = \"invalid input\"}; }\n\n    Log::logger->log(Log::DEBUG, \"tester: scrolling by {}\", by);\n\n    g_mouse->m_pointerEvents.axis.emit(IPointer::SAxisEvent{\n        .delta         = by,\n        .deltaDiscrete = 120,\n        .mouse         = true,\n    });\n\n    return {};\n}\n\nstatic SDispatchResult click(std::string in) {\n    CVarList2 data(std::move(in));\n\n    uint32_t  button;\n    bool      pressed;\n    try {\n        button  = std::stoul(std::string{data[0]});\n        pressed = std::stoul(std::string{data[1]}) == 1;\n    } catch (...) { return {.success = false, .error = \"invalid input\"}; }\n\n    Log::logger->log(Log::DEBUG, \"tester: mouse button {} state {}\", button, pressed);\n\n    g_mouse->m_pointerEvents.button.emit(IPointer::SButtonEvent{\n        .timeMs = sc<uint32_t>(Time::millis(Time::steadyNow())),\n        .button = button,\n        .state  = pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED,\n        .mouse  = true,\n    });\n\n    return {};\n}\n\nstatic SDispatchResult keybind(std::string in) {\n    CVarList2 data(std::move(in));\n    // 0 = release, 1 = press\n    bool press;\n    // See src/devices/IKeyboard.hpp : eKeyboardModifiers for modifier bitmasks\n    // 0 = none, eKeyboardModifiers is shifted to start at 1\n    uint32_t modifier;\n    // keycode\n    uint32_t key;\n    try {\n        press    = std::stoul(std::string{data[0]}) == 1;\n        modifier = std::stoul(std::string{data[1]});\n        key      = std::stoul(std::string{data[2]}) - 8; // xkb offset\n    } catch (...) { return {.success = false, .error = \"invalid input\"}; }\n\n    uint32_t modifierMask = 0;\n    if (modifier > 0)\n        modifierMask = 1 << (modifier - 1);\n    g_pInputManager->m_lastMods = modifierMask;\n    g_keyboard->sendKey(key, press);\n\n    return {};\n}\n\nstatic Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0;\n\n//\nstatic SDispatchResult addWindowRule(std::string in) {\n    windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect(\"plugin_rule\");\n\n    if (Desktop::Rule::windowEffects()->registerEffect(\"plugin_rule\") != windowRuleIDX)\n        return {.success = false, .error = \"re-registering returned a different id?\"};\n    return {};\n}\n\nstatic SDispatchResult checkWindowRule(std::string in) {\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"No window\"};\n\n    if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX))\n        return {.success = false, .error = \"No rule\"};\n\n    if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != \"effect\")\n        return {.success = false, .error = \"Effect isn't \\\"effect\\\"\"};\n\n    return {};\n}\n\nstatic Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0;\n\nstatic SDispatchResult                                       addLayerRule(std::string in) {\n    layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect(\"plugin_rule\");\n\n    if (Desktop::Rule::layerEffects()->registerEffect(\"plugin_rule\") != layerRuleIDX)\n        return {.success = false, .error = \"re-registering returned a different id?\"};\n    return {};\n}\n\nstatic SDispatchResult checkLayerRule(std::string in) {\n    if (g_pCompositor->m_layers.size() != 3)\n        return {.success = false, .error = \"Layers under test not here\"};\n\n    for (const auto& layer : g_pCompositor->m_layers) {\n        if (layer->m_namespace == \"rule-layer\") {\n\n            if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))\n                return {.success = false, .error = \"No rule\"};\n\n            if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != \"effect\")\n                return {.success = false, .error = \"Effect isn't \\\"effect\\\"\"};\n\n        } else if (layer->m_namespace == \"norule-layer\") {\n\n            if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))\n                return {.success = false, .error = \"Rule even though it shouldn't\"};\n\n        } else\n            return {.success = false, .error = \"Unrecognized layer\"};\n    }\n\n    return {};\n}\n\nstatic SDispatchResult floatingFocusOnFullscreen(std::string in) {\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"No window\"};\n\n    if (!PLASTWINDOW->m_isFloating)\n        return {.success = false, .error = \"Window must be floating\"};\n\n    if (PLASTWINDOW->m_alpha != 1.f)\n        return {.success = false, .error = \"floating window doesnt restore it opacity when focused on fullscreen workspace\"};\n\n    if (!PLASTWINDOW->m_createdOverFullscreen)\n        return {.success = false, .error = \"floating window doesnt get flagged as createdOverFullscreen\"};\n\n    return {};\n}\n\nAPICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {\n    PHANDLE = handle;\n\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:test\", ::test);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:snapmove\", ::snapMove);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:vkb\", ::vkb);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:alt\", ::pressAlt);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:gesture\", ::simulateGesture);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:scroll\", ::scroll);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:click\", ::click);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:keybind\", ::keybind);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:add_window_rule\", ::addWindowRule);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:check_window_rule\", ::checkWindowRule);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:add_layer_rule\", ::addLayerRule);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:check_layer_rule\", ::checkLayerRule);\n    HyprlandAPI::addDispatcherV2(PHANDLE, \"plugin:test:floating_focus_on_fullscreen\", ::floatingFocusOnFullscreen);\n\n    // init mouse\n    g_mouse = CTestMouse::create(false);\n    g_pInputManager->newMouse(g_mouse);\n\n    // init keyboard\n    g_keyboard = CTestKeyboard::create(false);\n    g_pInputManager->newKeyboard(g_keyboard);\n\n    return {\"hyprtestplugin\", \"hyprtestplugin\", \"Vaxry\", \"1.0\"};\n}\n\nAPICALL EXPORT void PLUGIN_EXIT() {\n    g_mouse->destroy();\n    g_mouse.reset();\n    g_keyboard->destroy();\n    g_keyboard.reset();\n}\n"
  },
  {
    "path": "hyprtester/protocols/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "hyprtester/src/Log.hpp",
    "content": "#pragma once\n#include <string>\n#include <format>\n#include <print>\n\nnamespace NLog {\n    template <typename... Args>\n    //NOLINTNEXTLINE\n    void log(std::format_string<Args...> fmt, Args&&... args) {\n        std::string logMsg = \"\";\n\n        logMsg += std::vformat(fmt.get(), std::make_format_args(args...));\n\n        std::println(\"{}\", logMsg);\n        std::fflush(stdout);\n    }\n}"
  },
  {
    "path": "hyprtester/src/hyprctlCompat.cpp",
    "content": "#include \"hyprctlCompat.hpp\"\n#include \"shared.hpp\"\n\n#include <pwd.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <unistd.h>\n\n#include <filesystem>\n#include <fstream>\n#include <algorithm>\n#include <csignal>\n#include <cerrno>\n#include <print>\n#include <hyprutils/memory/Casts.hpp>\nusing namespace Hyprutils::Memory;\n\nstatic int getUID() {\n    const auto UID   = getuid();\n    const auto PWUID = getpwuid(UID);\n    return PWUID ? PWUID->pw_uid : UID;\n}\n\nstatic std::string getRuntimeDir() {\n    const auto XDG = getenv(\"XDG_RUNTIME_DIR\");\n\n    if (!XDG) {\n        const std::string USERID = std::to_string(getUID());\n        return \"/run/user/\" + USERID + \"/hypr\";\n    }\n\n    return std::string{XDG} + \"/hypr\";\n}\n\nstd::vector<SInstanceData> instances() {\n    std::vector<SInstanceData> result;\n\n    try {\n        if (!std::filesystem::exists(getRuntimeDir()))\n            return {};\n    } catch (std::exception& e) { return {}; }\n\n    for (const auto& el : std::filesystem::directory_iterator(getRuntimeDir())) {\n        if (!el.is_directory() || !std::filesystem::exists(el.path().string() + \"/hyprland.lock\"))\n            continue;\n\n        // read lock\n        SInstanceData* data = &result.emplace_back();\n        data->id            = el.path().filename().string();\n\n        try {\n            data->time = std::stoull(data->id.substr(data->id.find_first_of('_') + 1, data->id.find_last_of('_') - (data->id.find_first_of('_') + 1)));\n        } catch (std::exception& e) { continue; }\n\n        // read file\n        std::ifstream ifs(el.path().string() + \"/hyprland.lock\");\n\n        int           i = 0;\n        for (std::string line; std::getline(ifs, line); ++i) {\n            if (i == 0) {\n                try {\n                    data->pid = std::stoull(line);\n                } catch (std::exception& e) { continue; }\n            } else if (i == 1) {\n                data->wlSocket = line;\n            } else\n                break;\n        }\n\n        ifs.close();\n    }\n\n    std::erase_if(result, [&](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });\n\n    std::sort(result.begin(), result.end(), [&](const auto& a, const auto& b) { return a.time < b.time; });\n\n    return result;\n}\n\nstd::string getFromSocket(const std::string& cmd) {\n    const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);\n\n    auto       t = timeval{.tv_sec = 5, .tv_usec = 0};\n    setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));\n\n    if (SERVERSOCKET < 0) {\n        std::println(\"socket: Couldn't open a socket (1)\");\n        return \"\";\n    }\n\n    sockaddr_un serverAddress = {0};\n    serverAddress.sun_family  = AF_UNIX;\n\n    std::string socketPath = getRuntimeDir() + \"/\" + HIS + \"/.socket.sock\";\n\n    strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);\n\n    if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {\n        std::println(\"Couldn't connect to {}. (3)\", socketPath);\n        return \"\";\n    }\n\n    auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());\n\n    if (sizeWritten < 0) {\n        std::println(\"Couldn't write (4)\");\n        return \"\";\n    }\n\n    std::string reply        = \"\";\n    char        buffer[8192] = {0};\n\n    sizeWritten = read(SERVERSOCKET, buffer, 8192);\n\n    if (sizeWritten < 0) {\n        if (errno == EWOULDBLOCK)\n            std::println(\"Hyprland IPC didn't respond in time\");\n        std::println(\"Couldn't read (5)\");\n        return \"\";\n    }\n\n    reply += std::string(buffer, sizeWritten);\n\n    while (sizeWritten == 8192) {\n        sizeWritten = read(SERVERSOCKET, buffer, 8192);\n        if (sizeWritten < 0) {\n            std::println(\"Couldn't read (5)\");\n            return \"\";\n        }\n        reply += std::string(buffer, sizeWritten);\n    }\n\n    close(SERVERSOCKET);\n\n    return reply;\n}"
  },
  {
    "path": "hyprtester/src/hyprctlCompat.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <string>\n#include <cstdint>\n\nstruct SInstanceData {\n    std::string id;\n    uint64_t    time;\n    uint64_t    pid;\n    std::string wlSocket;\n    bool        valid = true;\n};\n\nstd::vector<SInstanceData> instances();\nstd::string                getFromSocket(const std::string& cmd);"
  },
  {
    "path": "hyprtester/src/main.cpp",
    "content": "\n// This is a tester for Hyprland. It will launch the built binary in ./build/Hyprland\n// in headless mode and test various things.\n// for now it's quite basic and limited, but will be expanded in the future.\n\n// NOTE: This tester has to be ran from its directory!!\n\n// Some TODO:\n// - Add a plugin built alongside so that we can do more detailed tests (e.g. simulating keystrokes)\n// - test coverage\n// - maybe figure out a way to do some visual tests too?\n\n// Required runtime deps for checks:\n// - kitty\n// - xeyes\n\n#include \"shared.hpp\"\n#include \"hyprctlCompat.hpp\"\n#include \"tests/main/tests.hpp\"\n#include \"tests/clients/tests.hpp\"\n#include \"tests/plugin/plugin.hpp\"\n\n#include <filesystem>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <hyprutils/memory/Casts.hpp>\nusing namespace Hyprutils::Memory;\n\n#include <csignal>\n#include <cerrno>\n#include <chrono>\n#include <thread>\n#include <print>\n#include <string_view>\n#include <span>\n\n#include \"Log.hpp\"\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define SP CSharedPointer\n\nstatic int               ret = 0;\nstatic SP<CProcess>      hyprlandProc;\nstatic const std::string cwd = std::filesystem::current_path().string();\n\n//\nstatic bool launchHyprland(std::string configPath, std::string binaryPath) {\n    if (binaryPath == \"\") {\n        std::error_code ec;\n        if (!std::filesystem::exists(cwd + \"/../build/Hyprland\", ec) || ec) {\n            NLog::log(\"{}No Hyprland binary\", Colors::RED);\n            return false;\n        }\n\n        binaryPath = cwd + \"/../build/Hyprland\";\n    }\n\n    if (configPath == \"\") {\n        std::error_code ec;\n        if (!std::filesystem::exists(cwd + \"/test.conf\", ec) || ec) {\n            NLog::log(\"{}No test config\", Colors::RED);\n            return false;\n        }\n\n        configPath = cwd + \"/test.conf\";\n    }\n\n    NLog::log(\"{}Launching Hyprland\", Colors::YELLOW);\n    hyprlandProc = makeShared<CProcess>(binaryPath, std::vector<std::string>{\"--config\", configPath});\n    hyprlandProc->addEnv(\"HYPRLAND_HEADLESS_ONLY\", \"1\");\n\n    NLog::log(\"{}Launched async process\", Colors::YELLOW);\n\n    return hyprlandProc->runAsync();\n}\n\nstatic bool hyprlandAlive() {\n    NLog::log(\"{}hyprlandAlive\", Colors::YELLOW);\n    kill(hyprlandProc->pid(), 0);\n    return errno != ESRCH;\n}\n\nstatic void help() {\n    NLog::log(\"usage: hyprtester [arg [...]].\\n\");\n    NLog::log(R\"(Arguments:\n    --help              -h       - Show this message again\n    --config FILE       -c FILE  - Specify config file to use\n    --binary FILE       -b FILE  - Specify Hyprland binary to use\n    --plugin FILE       -p FILE  - Specify the location of the test plugin)\");\n}\n\nint main(int argc, char** argv, char** envp) {\n\n    std::string configPath = \"\";\n    std::string binaryPath = \"\";\n    std::string pluginPath = std::filesystem::current_path().string();\n\n    if (argc > 1) {\n        std::span<char*> args{argv + 1, sc<std::size_t>(argc - 1)};\n\n        for (auto it = args.begin(); it != args.end(); it++) {\n            std::string_view value = *it;\n\n            if (value == \"--config\" || value == \"-c\") {\n                if (std::next(it) == args.end()) {\n                    help();\n\n                    return 1;\n                }\n\n                configPath = *std::next(it);\n\n                try {\n                    configPath = std::filesystem::canonical(configPath);\n\n                    if (!std::filesystem::is_regular_file(configPath)) {\n                        throw std::exception();\n                    }\n                } catch (...) {\n                    std::println(stderr, \"[ ERROR ] Config file '{}' doesn't exist!\", configPath);\n                    help();\n\n                    return 1;\n                }\n\n                it++;\n\n                continue;\n            } else if (value == \"--binary\" || value == \"-b\") {\n                if (std::next(it) == args.end()) {\n                    help();\n\n                    return 1;\n                }\n\n                binaryPath = *std::next(it);\n\n                try {\n                    binaryPath = std::filesystem::canonical(binaryPath);\n\n                    if (!std::filesystem::is_regular_file(binaryPath)) {\n                        throw std::exception();\n                    }\n                } catch (...) {\n                    std::println(stderr, \"[ ERROR ] Binary '{}' doesn't exist!\", binaryPath);\n                    help();\n\n                    return 1;\n                }\n\n                it++;\n\n                continue;\n            } else if (value == \"--plugin\" || value == \"-p\") {\n                if (std::next(it) == args.end()) {\n                    help();\n\n                    return 1;\n                }\n\n                pluginPath = *std::next(it);\n\n                try {\n                    pluginPath = std::filesystem::canonical(pluginPath);\n\n                    if (!std::filesystem::is_regular_file(pluginPath)) {\n                        throw std::exception();\n                    }\n                } catch (...) {\n                    std::println(stderr, \"[ ERROR ] plugin '{}' doesn't exist!\", pluginPath);\n                    help();\n\n                    return 1;\n                }\n\n                it++;\n\n                continue;\n            } else if (value == \"--help\" || value == \"-h\") {\n                help();\n\n                return 0;\n            } else {\n                std::println(stderr, \"[ ERROR ] Unknown option '{}' !\", *it);\n                help();\n\n                return 1;\n            }\n        }\n    }\n\n    NLog::log(\"{}launching hl\", Colors::YELLOW);\n    if (!launchHyprland(configPath, binaryPath)) {\n        NLog::log(\"{}well it failed\", Colors::RED);\n        return 1;\n    }\n\n    // hyprland has launched, let's check if it's alive after 10s\n    std::this_thread::sleep_for(std::chrono::milliseconds(10000));\n    NLog::log(\"{}slept for 10s\", Colors::YELLOW);\n    if (!hyprlandAlive()) {\n        NLog::log(\"{}Hyprland failed to launch\", Colors::RED);\n        return 1;\n    }\n\n    // wonderful, we are in. Let's get the instance signature.\n    NLog::log(\"{}trying to get INSTANCES\", Colors::YELLOW);\n    const auto INSTANCES = instances();\n    if (INSTANCES.empty()) {\n        NLog::log(\"{}Hyprland failed to launch (2)\", Colors::RED);\n        return 1;\n    }\n\n    HIS       = INSTANCES.back().id;\n    WLDISPLAY = INSTANCES.back().wlSocket;\n\n    NLog::log(\"{}trying to get create headless output\", Colors::YELLOW);\n    getFromSocket(\"/output create headless\");\n\n    NLog::log(\"{}trying to load plugin\", Colors::YELLOW);\n    if (const auto R = getFromSocket(std::format(\"/plugin load {}\", pluginPath)); R != \"ok\") {\n        NLog::log(\"{}Failed to load the test plugin: {}\", Colors::RED, R);\n        getFromSocket(\"/dispatch exit 1\");\n        return 1;\n    }\n\n    NLog::log(\"{}Loaded plugin\", Colors::YELLOW);\n\n    NLog::log(\"{}Running main tests\", Colors::YELLOW);\n\n    for (const auto& fn : testFns) {\n        EXPECT(fn(), true);\n    }\n\n    NLog::log(\"{}Running protocol client tests\", Colors::YELLOW);\n\n    for (const auto& fn : clientTestFns) {\n        EXPECT(fn(), true);\n    }\n\n    NLog::log(\"{}running plugin test\", Colors::YELLOW);\n    EXPECT(testPlugin(), true);\n\n    NLog::log(\"{}running vkb test from plugin\", Colors::YELLOW);\n    EXPECT(testVkb(), true);\n\n    // kill hyprland\n    NLog::log(\"{}dispatching exit\", Colors::YELLOW);\n    getFromSocket(\"/dispatch exit\");\n\n    NLog::log(\"\\n{}Summary:\\n\\tPASSED: {}{}{}/{}\\n\\tFAILED: {}{}{}/{}\\n{}\", Colors::RESET, Colors::GREEN, TESTS_PASSED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, Colors::RED,\n              TESTS_FAILED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, (TESTS_FAILED > 0 ? std::string{Colors::RED} + \"\\nSome tests failed.\\n\" : \"\"));\n\n    kill(hyprlandProc->pid(), SIGKILL);\n\n    hyprlandProc.reset();\n\n    return ret || TESTS_FAILED;\n}\n"
  },
  {
    "path": "hyprtester/src/shared.hpp",
    "content": "// Stolen from hyprutils\n\n#pragma once\n#include <iostream>\n\ninline std::string HIS          = \"\";\ninline std::string WLDISPLAY    = \"\";\ninline int         TESTS_PASSED = 0;\ninline int         TESTS_FAILED = 0;\n\nnamespace Colors {\n    constexpr const char* RED     = \"\\x1b[31m\";\n    constexpr const char* GREEN   = \"\\x1b[32m\";\n    constexpr const char* YELLOW  = \"\\x1b[33m\";\n    constexpr const char* BLUE    = \"\\x1b[34m\";\n    constexpr const char* MAGENTA = \"\\x1b[35m\";\n    constexpr const char* CYAN    = \"\\x1b[36m\";\n    constexpr const char* RESET   = \"\\x1b[0m\";\n};\n\n#define EXPECT_MAX_DELTA(expr, desired, delta)                                                                                                                                     \\\n    if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) {                                                                                                          \\\n        NLog::log(\"{}Failed: {}{}, expected max delta of {}, got delta {} ({} - {}). Source: {}@{}.\", Colors::RED, Colors::RESET, #expr, delta, (RESULT - (desired)), RESULT,      \\\n                  desired, __FILE__, __LINE__);                                                                                                                                    \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{}. Got {}\", Colors::GREEN, Colors::RESET, #expr, (RESULT - (desired)));                                                                            \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define EXPECT(expr, val)                                                                                                                                                          \\\n    if (const auto RESULT = expr; RESULT != (val)) {                                                                                                                               \\\n        NLog::log(\"{}Failed: {}{}, expected {}, got {}. Source: {}@{}.\", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__);                                      \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{}. Got {}\", Colors::GREEN, Colors::RESET, #expr, val);                                                                                             \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define EXPECT_NOT(expr, val)                                                                                                                                                      \\\n    if (const auto RESULT = expr; RESULT == (val)) {                                                                                                                               \\\n        NLog::log(\"{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.\", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__);                                  \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{}. Got {}\", Colors::GREEN, Colors::RESET, #expr, val);                                                                                             \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define EXPECT_VECTOR2D(expr, val)                                                                                                                                                 \\\n    do {                                                                                                                                                                           \\\n        const auto& RESULT   = expr;                                                                                                                                               \\\n        const auto& EXPECTED = val;                                                                                                                                                \\\n        if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) {                                                                                 \\\n            NLog::log(\"{}Failed: {}{}, expected [{}, {}], got [{}, {}]. Source: {}@{}.\", Colors::RED, Colors::RESET, #expr, EXPECTED.x, EXPECTED.y, RESULT.x, RESULT.y, __FILE__,  \\\n                      __LINE__);                                                                                                                                                   \\\n            ret = 1;                                                                                                                                                               \\\n            TESTS_FAILED++;                                                                                                                                                        \\\n        } else {                                                                                                                                                                   \\\n            NLog::log(\"{}Passed: {}{}. Got [{}, {}].\", Colors::GREEN, Colors::RESET, #expr, RESULT.x, RESULT.y);                                                                   \\\n            TESTS_PASSED++;                                                                                                                                                        \\\n        }                                                                                                                                                                          \\\n    } while (0)\n\n#define EXPECT_CONTAINS(haystack, needle)                                                                                                                                          \\\n    if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) {                                                                                                 \\\n        NLog::log(\"{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\\n{}\", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__,             \\\n                  std::string{haystack});                                                                                                                                          \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{} contains {}.\", Colors::GREEN, Colors::RESET, #haystack, EXPECTED);                                                                               \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define EXPECT_NOT_CONTAINS(haystack, needle)                                                                                                                                      \\\n    if (std::string{haystack}.contains(needle)) {                                                                                                                                  \\\n        NLog::log(\"{}Failed: {}{} shouldn't contain {} but does. Source: {}@{}. Haystack is:\\n{}\", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__,             \\\n                  std::string{haystack});                                                                                                                                          \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{} doesn't contain {}.\", Colors::GREEN, Colors::RESET, #haystack, #needle);                                                                         \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define EXPECT_STARTS_WITH(str, what)                                                                                                                                              \\\n    if (!std::string{str}.starts_with(what)) {                                                                                                                                     \\\n        NLog::log(\"{}Failed: {}{} should start with {} but doesn't. Source: {}@{}. String is:\\n{}\", Colors::RED, Colors::RESET, #str, #what, __FILE__, __LINE__,                   \\\n                  std::string{str});                                                                                                                                               \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{} starts with {}.\", Colors::GREEN, Colors::RESET, #str, #what);                                                                                    \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define EXPECT_COUNT_STRING(str, what, no)                                                                                                                                         \\\n    if (Tests::countOccurrences(str, what) != no) {                                                                                                                                \\\n        NLog::log(\"{}Failed: {}{} should contain {} {} times, but doesn't. Source: {}@{}. String is:\\n{}\", Colors::RED, Colors::RESET, #str, #what, no, __FILE__, __LINE__,        \\\n                  std::string{str});                                                                                                                                               \\\n        ret = 1;                                                                                                                                                                   \\\n        TESTS_FAILED++;                                                                                                                                                            \\\n    } else {                                                                                                                                                                       \\\n        NLog::log(\"{}Passed: {}{} contains {} {} times.\", Colors::GREEN, Colors::RESET, #str, #what, no);                                                                          \\\n        TESTS_PASSED++;                                                                                                                                                            \\\n    }\n\n#define OK(x) EXPECT(x, \"ok\")\n"
  },
  {
    "path": "hyprtester/src/tests/clients/.gitignore",
    "content": "build.hpp\n"
  },
  {
    "path": "hyprtester/src/tests/clients/child-window.cpp",
    "content": "#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n#include \"build.hpp\"\n\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <hyprutils/os/Process.hpp>\n\n#include <sys/poll.h>\n#include <unistd.h>\n#include <csignal>\n#include <thread>\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define SP CSharedPointer\n\nstruct SClient {\n    SP<CProcess>           proc;\n    std::array<char, 1024> readBuf;\n    CFileDescriptor        readFd, writeFd;\n    struct pollfd          fds;\n};\n\nstatic int  ret = 0;\n\nstatic bool waitForWindow(SP<CProcess> proc, int windowsBefore) {\n    int counter = 0;\n    while (Tests::processAlive(proc->pid()) && Tests::windowCount() == windowsBefore) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50)\n            return false;\n    }\n\n    NLog::log(\"{}Waited {} milliseconds for window to open\", Colors::YELLOW, counter * 100);\n    return Tests::processAlive(proc->pid());\n}\n\nstatic bool startClient(SClient& client) {\n    NLog::log(\"{}Attempting to start child-window client\", Colors::YELLOW);\n\n    client.proc = makeShared<CProcess>(binaryDir + \"/child-window\", std::vector<std::string>{});\n\n    client.proc->addEnv(\"WAYLAND_DISPLAY\", WLDISPLAY);\n\n    int procInPipeFd[2], procOutPipeFd[2];\n    if (pipe(procInPipeFd) != 0 || pipe(procOutPipeFd) != 0) {\n        NLog::log(\"{}Unable to open pipe to client\", Colors::RED);\n        return false;\n    }\n\n    client.writeFd = CFileDescriptor(procInPipeFd[1]);\n    client.proc->setStdinFD(procInPipeFd[0]);\n\n    client.readFd = CFileDescriptor(procOutPipeFd[0]);\n    client.proc->setStdoutFD(procOutPipeFd[1]);\n\n    if (!client.proc->runAsync()) {\n        NLog::log(\"{}Failed to run client\", Colors::RED);\n        return false;\n    }\n\n    close(procInPipeFd[0]);\n    close(procOutPipeFd[1]);\n\n    if (!waitForWindow(client.proc, Tests::windowCount())) {\n        NLog::log(\"{}Window took too long to open\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Started child-window client\", Colors::YELLOW);\n    return true;\n}\n\nstatic void stopClient(SClient& client) {\n    std::string cmd = \"exit\\n\";\n    write(client.writeFd.get(), cmd.c_str(), cmd.length());\n\n    kill(client.proc->pid(), SIGKILL);\n    client.proc.reset();\n}\n\nstatic bool createChild(SClient& client) {\n    std::string cmd = \"toplevel\\n\";\n    if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length())\n        return false;\n\n    if (!waitForWindow(client.proc, Tests::windowCount()))\n        NLog::log(\"{}Child window took too long to open\", Colors::RED);\n\n    if (getFromSocket(\"/dispatch focuswindow class:child-test-child\") != \"ok\") {\n        NLog::log(\"{}Failed to focus child window\", Colors::RED);\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool test() {\n    SClient client;\n\n    if (!startClient(client))\n        return false;\n    OK(getFromSocket(\"/dispatch setfloating class:child-test-parent\"));\n    OK(getFromSocket(\"/dispatch pin class:child-test-parent\"));\n\n    createChild(client);\n    EXPECT(Tests::windowCount(), 2)\n    EXPECT_COUNT_STRING(getFromSocket(\"/clients\"), \"pinned: 1\", 2);\n\n    stopClient(client);\n    NLog::log(\"{}Reloading config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    // test that child windows (shouldBeFloated) are not auto-grouped\n    NLog::log(\"{}Test child windows are not auto-grouped\", Colors::GREEN);\n    auto kitty = Tests::spawnKitty();\n    if (!kitty) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    // create group and enable auto-grouping\n    OK(getFromSocket(\"/dispatch togglegroup\"));\n    OK(getFromSocket(\"/keyword group:auto_group true\"));\n\n    SClient client2;\n    if (!startClient(client2))\n        return false;\n\n    EXPECT(Tests::windowCount(), 2);\n    createChild(client2);\n    EXPECT(Tests::windowCount(), 3);\n\n    // child has set_parent so shouldBeFloated returns true, it should not be auto-grouped\n    EXPECT_COUNT_STRING(getFromSocket(\"/clients\"), \"grouped: 0\", 1);\n\n    stopClient(client2);\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    return !ret;\n}\n\nREGISTER_CLIENT_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/clients/pointer-scroll.cpp",
    "content": "#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n#include \"build.hpp\"\n\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <hyprutils/os/Process.hpp>\n\n#include <sys/poll.h>\n#include <unistd.h>\n#include <csignal>\n#include <thread>\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define SP CSharedPointer\n\nstruct SClient {\n    SP<CProcess>           proc;\n    std::array<char, 1024> readBuf;\n    CFileDescriptor        readFd, writeFd;\n    struct pollfd          fds;\n};\n\nstatic int  ret = 0;\n\nstatic bool startClient(SClient& client) {\n    client.proc = makeShared<CProcess>(binaryDir + \"/pointer-scroll\", std::vector<std::string>{});\n\n    client.proc->addEnv(\"WAYLAND_DISPLAY\", WLDISPLAY);\n\n    int pipeFds1[2], pipeFds2[2];\n    if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) {\n        NLog::log(\"{}Unable to open pipe to client\", Colors::RED);\n        return false;\n    }\n\n    client.writeFd = CFileDescriptor(pipeFds1[1]);\n    client.proc->setStdinFD(pipeFds1[0]);\n\n    client.readFd = CFileDescriptor(pipeFds2[0]);\n    client.proc->setStdoutFD(pipeFds2[1]);\n\n    const int COUNT_BEFORE = Tests::windowCount();\n    client.proc->runAsync();\n\n    close(pipeFds1[0]);\n    close(pipeFds2[1]);\n\n    client.fds = {.fd = client.readFd.get(), .events = POLLIN};\n    if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN))\n        return false;\n\n    client.readBuf.fill(0);\n    if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)\n        return false;\n\n    std::string ret = std::string{client.readBuf.data()};\n    if (ret.find(\"started\") == std::string::npos) {\n        NLog::log(\"{}Failed to start pointer-scroll client, read {}\", Colors::RED, ret);\n        return false;\n    }\n\n    // wait for window to appear\n    int counter = 0;\n    while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50) {\n            NLog::log(\"{}pointer-scroll client took too long to open\", Colors::RED);\n            return false;\n        }\n    }\n\n    if (getFromSocket(std::format(\"/dispatch setprop pid:{} no_anim 1\", client.proc->pid())) != \"ok\") {\n        NLog::log(\"{}Failed to disable animations for client window\", Colors::RED, ret);\n        return false;\n    }\n\n    if (getFromSocket(std::format(\"/dispatch focuswindow pid:{}\", client.proc->pid())) != \"ok\") {\n        NLog::log(\"{}Failed to focus pointer-scroll client\", Colors::RED, ret);\n        return false;\n    }\n\n    NLog::log(\"{}Started pointer-scroll client\", Colors::YELLOW);\n\n    return true;\n}\n\nstatic void stopClient(SClient& client) {\n    std::string cmd = \"exit\\n\";\n    write(client.writeFd.get(), cmd.c_str(), cmd.length());\n\n    kill(client.proc->pid(), SIGKILL);\n    client.proc.reset();\n}\n\nstatic int getLastDelta(SClient& client) {\n    std::string cmd = \"hypr\";\n    if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length())\n        return false;\n\n    if (poll(&client.fds, 1, 1500) != 1 || !(client.fds.revents & POLLIN))\n        return false;\n    ssize_t bytesRead = read(client.fds.fd, client.readBuf.data(), 1023);\n    if (bytesRead == -1)\n        return false;\n\n    client.readBuf[bytesRead] = 0;\n    std::string received      = std::string{client.readBuf.data()};\n    received.pop_back();\n\n    try {\n        return std::stoi(received);\n    } catch (...) { return -1; }\n}\n\nstatic bool sendScroll(int delta) {\n    return getFromSocket(std::format(\"/dispatch plugin:test:scroll {}\", delta)) == \"ok\";\n}\n\nstatic bool test() {\n    SClient client;\n\n    if (!startClient(client))\n        return false;\n\n    EXPECT(getFromSocket(\"/keyword input:emulate_discrete_scroll 0\"), \"ok\");\n\n    EXPECT(sendScroll(10), true);\n    EXPECT(getLastDelta(client), 10);\n\n    EXPECT(getFromSocket(\"/keyword input:scroll_factor 2\"), \"ok\");\n    EXPECT(sendScroll(10), true);\n    EXPECT(getLastDelta(client), 20);\n\n    EXPECT(getFromSocket(\"r/keyword device[test-mouse-1]:scroll_factor 3\"), \"ok\");\n    EXPECT(sendScroll(10), true);\n    EXPECT(getLastDelta(client), 30);\n\n    EXPECT(getFromSocket(\"r/dispatch setprop active scroll_mouse 4\"), \"ok\");\n    EXPECT(sendScroll(10), true);\n    EXPECT(getLastDelta(client), 40);\n\n    stopClient(client);\n\n    NLog::log(\"{}Reloading the config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_CLIENT_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/clients/pointer-warp.cpp",
    "content": "#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n#include \"build.hpp\"\n\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <hyprutils/os/Process.hpp>\n\n#include <sys/poll.h>\n#include <unistd.h>\n#include <csignal>\n#include <thread>\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define SP CSharedPointer\n\nstruct SClient {\n    SP<CProcess>           proc;\n    std::array<char, 1024> readBuf;\n    CFileDescriptor        readFd, writeFd;\n    struct pollfd          fds;\n};\n\nstatic int  ret = 0;\n\nstatic bool startClient(SClient& client) {\n    client.proc = makeShared<CProcess>(binaryDir + \"/pointer-warp\", std::vector<std::string>{});\n\n    client.proc->addEnv(\"WAYLAND_DISPLAY\", WLDISPLAY);\n\n    int pipeFds1[2], pipeFds2[2];\n    if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) {\n        NLog::log(\"{}Unable to open pipe to client\", Colors::RED);\n        return false;\n    }\n\n    client.writeFd = CFileDescriptor(pipeFds1[1]);\n    client.proc->setStdinFD(pipeFds1[0]);\n\n    client.readFd = CFileDescriptor(pipeFds2[0]);\n    client.proc->setStdoutFD(pipeFds2[1]);\n\n    const int COUNT_BEFORE = Tests::windowCount();\n    client.proc->runAsync();\n\n    close(pipeFds1[0]);\n    close(pipeFds2[1]);\n\n    client.fds = {.fd = client.readFd.get(), .events = POLLIN};\n    if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN))\n        return false;\n\n    client.readBuf.fill(0);\n    if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)\n        return false;\n\n    std::string ret = std::string{client.readBuf.data()};\n    if (ret.find(\"started\") == std::string::npos) {\n        NLog::log(\"{}Failed to start pointer-warp client, read {}\", Colors::RED, ret);\n        return false;\n    }\n\n    // wait for window to appear\n    int counter = 0;\n    while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50) {\n            NLog::log(\"{}pointer-warp client took too long to open\", Colors::RED);\n            return false;\n        }\n    }\n\n    if (getFromSocket(std::format(\"/dispatch setprop pid:{} no_anim 1\", client.proc->pid())) != \"ok\") {\n        NLog::log(\"{}Failed to disable animations for client window\", Colors::RED, ret);\n        return false;\n    }\n\n    if (getFromSocket(std::format(\"/dispatch focuswindow pid:{}\", client.proc->pid())) != \"ok\") {\n        NLog::log(\"{}Failed to focus pointer-warp client\", Colors::RED, ret);\n        return false;\n    }\n\n    NLog::log(\"{}Started pointer-warp client\", Colors::YELLOW);\n\n    return true;\n}\n\nstatic void stopClient(SClient& client) {\n    std::string cmd = \"exit\\n\";\n    write(client.writeFd.get(), cmd.c_str(), cmd.length());\n\n    kill(client.proc->pid(), SIGKILL);\n    client.proc.reset();\n}\n\n// format is like below\n// \"warp 20 20\\n\" would ask to warp cursor to x=20,y=20 in surface local coords\nstatic bool sendWarp(SClient& client, int x, int y) {\n    std::string cmd = std::format(\"warp {} {}\\n\", x, y);\n    if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length())\n        return false;\n\n    if (poll(&client.fds, 1, 1500) != 1 || !(client.fds.revents & POLLIN))\n        return false;\n    ssize_t bytesRead = read(client.fds.fd, client.readBuf.data(), 1023);\n    if (bytesRead == -1)\n        return false;\n\n    client.readBuf[bytesRead] = 0;\n    std::string recieved      = std::string{client.readBuf.data()};\n    recieved.pop_back();\n\n    return true;\n}\n\nstatic bool isCursorPos(int x, int y) {\n    // TODO: add a better way to do this using test plugin?\n    std::string res = getFromSocket(\"/cursorpos\");\n    if (res == \"error\") {\n        NLog::log(\"{}Cursorpos err'd: {}\", Colors::RED, res);\n        return false;\n    }\n\n    auto it = res.find_first_of(' ');\n    if (res.at(it - 1) != ',') {\n        NLog::log(\"{}Cursorpos err'd: {}\", Colors::RED, res);\n        return false;\n    }\n\n    int cursorX = std::stoi(res.substr(0, it - 1));\n    int cursorY = std::stoi(res.substr(it + 1));\n\n    // somehow this is always gives 1 less than surfbox->pos()??\n    res = getFromSocket(\"/activewindow\");\n    it  = res.find(\"at: \") + 4;\n    res = res.substr(it, res.find_first_of('\\n', it) - it);\n\n    it          = res.find_first_of(',');\n    int clientX = cursorX - std::stoi(res.substr(0, it)) + 1;\n    int clientY = cursorY - std::stoi(res.substr(it + 1)) + 1;\n\n    return clientX == x && clientY == y;\n}\n\nstatic bool test() {\n    SClient client;\n\n    if (!startClient(client))\n        return false;\n\n    EXPECT(sendWarp(client, 100, 100), true);\n    EXPECT(isCursorPos(100, 100), true);\n\n    EXPECT(sendWarp(client, 0, 0), true);\n    EXPECT(isCursorPos(0, 0), true);\n\n    EXPECT(sendWarp(client, 200, 200), true);\n    EXPECT(isCursorPos(200, 200), true);\n\n    EXPECT(sendWarp(client, 100, -100), true);\n    EXPECT(isCursorPos(200, 200), true);\n\n    EXPECT(sendWarp(client, 234, 345), true);\n    EXPECT(isCursorPos(234, 345), true);\n\n    EXPECT(sendWarp(client, -1, -1), true);\n    EXPECT(isCursorPos(234, 345), true);\n\n    EXPECT(sendWarp(client, 1, -1), true);\n    EXPECT(isCursorPos(234, 345), true);\n\n    EXPECT(sendWarp(client, 13, 37), true);\n    EXPECT(isCursorPos(13, 37), true);\n\n    EXPECT(sendWarp(client, -100, 100), true);\n    EXPECT(isCursorPos(13, 37), true);\n\n    EXPECT(sendWarp(client, -1, 1), true);\n    EXPECT(isCursorPos(13, 37), true);\n\n    stopClient(client);\n\n    NLog::log(\"{}Reloading the config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_CLIENT_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/clients/shortcut-inhibitor.cpp",
    "content": "#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n#include \"build.hpp\"\n\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <hyprutils/os/Process.hpp>\n\n#include <sys/poll.h>\n#include <csignal>\n#include <thread>\n#include <filesystem>\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define SP CSharedPointer\n\nstruct SClient {\n    SP<CProcess>           proc;\n    std::array<char, 1024> readBuf;\n    CFileDescriptor        readFd, writeFd;\n    struct pollfd          fds;\n};\n\nstatic int  ret = 0;\n\nstatic bool startClient(SClient& client) {\n    Tests::killAllWindows();\n    client.proc = makeShared<CProcess>(binaryDir + \"/shortcut-inhibitor\", std::vector<std::string>{});\n\n    client.proc->addEnv(\"WAYLAND_DISPLAY\", WLDISPLAY);\n\n    int pipeFds1[2], pipeFds2[2];\n    if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) {\n        NLog::log(\"{}Unable to open pipe to client\", Colors::RED);\n        return false;\n    }\n\n    client.writeFd = CFileDescriptor(pipeFds1[1]);\n    client.proc->setStdinFD(pipeFds1[0]);\n\n    client.readFd = CFileDescriptor(pipeFds2[0]);\n    client.proc->setStdoutFD(pipeFds2[1]);\n\n    const int COUNT_BEFORE = Tests::windowCount();\n    client.proc->runAsync();\n\n    close(pipeFds1[0]);\n    close(pipeFds2[1]);\n\n    client.fds = {.fd = client.readFd.get(), .events = POLLIN};\n    if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) {\n        NLog::log(\"{}shortcut-inhibitor client failed poll\", Colors::RED);\n        return false;\n    }\n\n    client.readBuf.fill(0);\n    if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) {\n        NLog::log(\"{}shortcut-inhibitor client read failed\", Colors::RED);\n        return false;\n    }\n\n    std::string ret = std::string{client.readBuf.data()};\n    if (ret.find(\"started\") == std::string::npos) {\n        NLog::log(\"{}Failed to start shortcut-inhibitor client, read {}\", Colors::RED, ret);\n        return false;\n    }\n\n    // wait for window to appear\n    int counter = 0;\n    while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50) {\n            NLog::log(\"{}shortcut-inhibitor client took too long to open\", Colors::RED);\n            return false;\n        }\n    }\n\n    if (!Tests::processAlive(client.proc->pid())) {\n        NLog::log(\"{}shortcut-inhibitor client not alive\", Colors::RED);\n        return false;\n    }\n\n    if (getFromSocket(std::format(\"/dispatch focuswindow pid:{}\", client.proc->pid())) != \"ok\") {\n        NLog::log(\"{}Failed to focus shortcut-inhibitor client\", Colors::RED, ret);\n        return false;\n    }\n\n    std::string command = \"on\\n\";\n    if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) {\n        NLog::log(\"{}shortcut-inhibitor client write failed\", Colors::RED);\n        return false;\n    }\n\n    client.readBuf.fill(0);\n    if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)\n        return false;\n\n    ret = std::string{client.readBuf.data()};\n    if (ret.find(\"inhibiting\") == std::string::npos) {\n        NLog::log(\"{}shortcut-inhibitor client didn't return inhibiting\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Started shortcut-inhibitor client\", Colors::YELLOW);\n\n    return true;\n}\n\nstatic void stopClient(SClient& client) {\n    std::string cmd = \"off\\n\";\n    write(client.writeFd.get(), cmd.c_str(), cmd.length());\n\n    kill(client.proc->pid(), SIGKILL);\n    client.proc.reset();\n}\n\nstatic std::string flagFile = \"/tmp/hyprtester-keybinds.txt\";\n\nstatic bool        checkFlag() {\n    bool exists = std::filesystem::exists(flagFile);\n    std::filesystem::remove(flagFile);\n    return exists;\n}\n\nstatic bool attemptCheckFlag(int attempts, int intervalMs) {\n    for (int i = 0; i < attempts; i++) {\n        if (checkFlag())\n            return true;\n\n        std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));\n    }\n\n    return false;\n}\n\nstatic bool test() {\n    SClient client;\n    if (!startClient(client))\n        return false;\n\n    NLog::log(\"{}Testing keybinds\", Colors::GREEN);\n    //basic keybind test\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bind SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    EXPECT(attemptCheckFlag(20, 50), false);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n\n    //keybind bypass flag test\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindp SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    EXPECT(attemptCheckFlag(20, 50), true);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n\n    NLog::log(\"{}Testing gestures\", Colors::GREEN);\n    //basic gesture test\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,3\"));\n    EXPECT_NOT_CONTAINS(getFromSocket(\"/activewindow\"), \"floating: 1\");\n\n    //gesture bypass flag test\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,2\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"floating: 1\");\n\n    stopClient(client);\n\n    NLog::log(\"{}Reloading the config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_CLIENT_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/clients/tests.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <functional>\n\ninline std::vector<std::function<bool()>> clientTestFns;\n\n#define REGISTER_CLIENT_TEST_FN(fn)                                                                                                                                                \\\n    static auto _register_fn = [] {                                                                                                                                                \\\n        clientTestFns.emplace_back(fn);                                                                                                                                            \\\n        return 1;                                                                                                                                                                  \\\n    }();\n"
  },
  {
    "path": "hyprtester/src/tests/main/animations.cpp",
    "content": "#include \"../../Log.hpp\"\n#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\nstatic bool test() {\n    NLog::log(\"{}Testing animations\", Colors::GREEN);\n\n    auto str = getFromSocket(\"/animations\");\n    NLog::log(\"{}Testing bezier curve output from `hyprctl animations`\", Colors::YELLOW);\n    {EXPECT_CONTAINS(str, std::format(\"beziers:\\n\\n\\tname: quick\\n\\t\\tX0: 0.15\\n\\t\\tY0: 0.00\\n\\t\\tX1: 0.10\\n\\t\\tY1: 1.00\"))};\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/colors.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n\nstatic int  ret = 0;\n\nstatic bool test() {\n    NLog::log(\"{}Testing hyprctl monitors\", Colors::GREEN);\n\n    std::string monitorsSpec = getFromSocket(\"j/monitors\");\n    EXPECT_CONTAINS(monitorsSpec, R\"(\"colorManagementPreset\": \"srgb\")\");\n\n    EXPECT_CONTAINS(getFromSocket(\"/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide\"), \"ok\")\n    monitorsSpec = getFromSocket(\"j/monitors\");\n    EXPECT_CONTAINS(monitorsSpec, R\"(\"colorManagementPreset\": \"wide\")\");\n\n    EXPECT_CONTAINS(getFromSocket(\"/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,srgb,sdrbrightness,1.2,sdrsaturation,0.98\"), \"ok\")\n    monitorsSpec = getFromSocket(\"j/monitors\");\n    EXPECT_CONTAINS(monitorsSpec, R\"(\"colorManagementPreset\": \"srgb\")\");\n    EXPECT_CONTAINS(monitorsSpec, R\"(\"sdrBrightness\": 1.20)\");\n    EXPECT_CONTAINS(monitorsSpec, R\"(\"sdrSaturation\": 0.98)\");\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/dwindle.cpp",
    "content": "#include \"../shared.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"tests.hpp\"\n\nstatic int  ret = 0;\n\nstatic void testFloatClamp() {\n    for (auto const& win : {\"a\", \"b\", \"c\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    OK(getFromSocket(\"/keyword dwindle:force_split 2\"));\n    OK(getFromSocket(\"/keyword monitor HEADLESS-2, addreserved, 0, 20, 0, 20\"));\n    OK(getFromSocket(\"/dispatch focuswindow class:c\"));\n    OK(getFromSocket(\"/dispatch setfloating class:c\"));\n    OK(getFromSocket(\"/dispatch resizewindowpixel exact 1200 900,class:c\"));\n    OK(getFromSocket(\"/dispatch settiled class:c\"));\n    OK(getFromSocket(\"/dispatch setfloating class:c\"));\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 698,158\");\n        EXPECT_CONTAINS(str, \"size: 1200,900\");\n    }\n\n    OK(getFromSocket(\"/keyword dwindle:force_split 0\"));\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/reload\"));\n}\n\nstatic void test13349() {\n\n    // Test if dwindle properly uses a focal point to place a new window.\n    // exposed by #13349 as a regression from #12890\n\n    for (auto const& win : {\"a\", \"b\", \"c\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:c\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 967,547\");\n        EXPECT_CONTAINS(str, \"size: 931,511\");\n    }\n\n    OK(getFromSocket(\"/dispatch movewindow l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,547\");\n        EXPECT_CONTAINS(str, \"size: 931,511\");\n    }\n\n    OK(getFromSocket(\"/dispatch movewindow r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 967,547\");\n        EXPECT_CONTAINS(str, \"size: 931,511\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic void testSplit() {\n    // Test various split methods\n\n    Tests::spawnKitty(\"a\");\n\n    // these must not crash\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg swapsplit\"), \"ok\");\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg splitratio 1 exact\"), \"ok\");\n\n    Tests::spawnKitty(\"b\");\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n    OK(getFromSocket(\"/dispatch layoutmsg splitratio -0.2\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 743,1036\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg splitratio 1.6 exact\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1495,1036\");\n    }\n\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg splitratio fhne exact\"), \"ok\");\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg splitratio exact\"), \"ok\");\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg splitratio -....9\"), \"ok\");\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg splitratio ..9\"), \"ok\");\n    EXPECT_NOT(getFromSocket(\"/dispatch layoutmsg splitratio\"), \"ok\");\n\n    OK(getFromSocket(\"/dispatch layoutmsg togglesplit\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876,823\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg swapsplit\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,859\");\n        EXPECT_CONTAINS(str, \"size: 1876,199\");\n    }\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic void testRotatesplit() {\n    OK(getFromSocket(\"r/keyword general:gaps_in 0\"));\n    OK(getFromSocket(\"r/keyword general:gaps_out 0\"));\n    OK(getFromSocket(\"r/keyword general:border_size 0\"));\n\n    for (auto const& win : {\"a\", \"b\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,0\");\n        EXPECT_CONTAINS(str, \"size: 960,1080\");\n    }\n\n    // test 4 repeated rotations by 90 degrees\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,0\");\n        EXPECT_CONTAINS(str, \"size: 1920,540\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 960,0\");\n        EXPECT_CONTAINS(str, \"size: 960,1080\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,540\");\n        EXPECT_CONTAINS(str, \"size: 1920,540\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,0\");\n        EXPECT_CONTAINS(str, \"size: 960,1080\");\n    }\n\n    // test different angles\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit 180\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 960,0\");\n        EXPECT_CONTAINS(str, \"size: 960,1080\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit 270\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,540\");\n        EXPECT_CONTAINS(str, \"size: 1920,540\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit 360\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,0\");\n        EXPECT_CONTAINS(str, \"size: 1920,540\");\n    }\n\n    // test negative angles\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit -90\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 0,0\");\n        EXPECT_CONTAINS(str, \"size: 960,1080\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg rotatesplit -180\"));\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 960,0\");\n        EXPECT_CONTAINS(str, \"size: 960,1080\");\n    }\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/reload\"));\n}\n\nstatic void testForceSplitOnMoveToWorkspace() {\n    OK(getFromSocket(\"/dispatch workspace 2\"));\n    EXPECT(!!Tests::spawnKitty(\"kitty\"), true);\n\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    EXPECT(!!Tests::spawnKitty(\"kitty\"), true);\n    std::string posBefore = Tests::getWindowAttribute(getFromSocket(\"/activewindow\"), \"at:\");\n\n    OK(getFromSocket(\"/keyword dwindle:force_split 2\"));\n    OK(getFromSocket(\"/dispatch movecursortocorner 3\")); // top left\n    OK(getFromSocket(\"/dispatch movetoworkspace 2\"));\n\n    // Should be moved to the right, so the position should change\n    std::string activeWindow = getFromSocket(\"/activewindow\");\n    EXPECT(activeWindow.contains(posBefore), false);\n\n    // clean up\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n    Tests::waitUntilWindowsN(0);\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing Dwindle layout\", Colors::GREEN);\n\n    // test\n    NLog::log(\"{}Testing float clamp\", Colors::GREEN);\n    testFloatClamp();\n\n    NLog::log(\"{}Testing #13349\", Colors::GREEN);\n    test13349();\n\n    NLog::log(\"{}Testing splits\", Colors::GREEN);\n    testSplit();\n\n    NLog::log(\"{}Testing rotatesplit\", Colors::GREEN);\n    testRotatesplit();\n\n    NLog::log(\"{}Testing force_split on move to workspace\", Colors::GREEN);\n    testForceSplitOnMoveToWorkspace();\n\n    // clean up\n    NLog::log(\"Cleaning up\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace 1\");\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/main/exec.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <chrono>\n#include <format>\n#include <thread>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nconst static auto SLEEP_DURATIONS = std::array{1, 10};\n\nstatic bool       test() {\n    NLog::log(\"{}Testing process spawning\", Colors::GREEN);\n\n    for (const auto duration : SLEEP_DURATIONS) {\n        // Note: POSIX sleep does not support fractional seconds, so\n        // can't sleep for less than 1 second.\n        OK(getFromSocket(std::format(\"/dispatch exec sleep {}\", duration)));\n\n        // Ensure that sleep is our child\n        const std::string sleepPidS = Tests::execAndGet(\"pgrep sleep\");\n        pid_t             sleepPid;\n        try {\n            sleepPid = std::stoull(sleepPidS);\n        } catch (...) {\n            NLog::log(\"{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'\", Colors::RED, sleepPidS);\n            continue;\n        }\n\n        const std::string sleepParentComm = Tests::execAndGet(\"cat \\\"/proc/$(ps -o ppid:1= -p \" + sleepPidS + \")/comm\\\"\");\n        NLog::log(\"{}Expecting that sleep's parent is Hyprland\", Colors::YELLOW);\n        EXPECT_CONTAINS(sleepParentComm, \"Hyprland\");\n\n        std::this_thread::sleep_for(std::chrono::seconds(duration));\n\n        // Ensure that sleep did not become a zombie\n        EXPECT(Tests::processAlive(sleepPid), false);\n\n        // kill all\n        NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n        Tests::killAllWindows();\n\n        NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n        EXPECT(Tests::windowCount(), 0);\n\n        return !ret;\n    }\n\n    return false;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/gestures.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <print>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nstatic bool waitForWindowCount(int expectedWindowCnt, std::string_view expectation, int waitMillis = 100, int maxWaitCnt = 50) {\n    int counter = 0;\n    while (Tests::windowCount() != expectedWindowCnt) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(waitMillis));\n\n        if (counter > maxWaitCnt) {\n            NLog::log(\"{}Unmet expectation: {}\", Colors::RED, expectation);\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing gestures\", Colors::GREEN);\n\n    EXPECT(Tests::windowCount(), 0);\n\n    // test on workspace \"window\"\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace 1\"); // no OK: we might be on 1 already\n\n    Tests::spawnKitty();\n    EXPECT(Tests::windowCount(), 1);\n\n    // Give the shell a moment to initialize\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture up,5\"));\n    OK(getFromSocket(\"/dispatch plugin:test:gesture down,5\"));\n    OK(getFromSocket(\"/dispatch plugin:test:gesture left,5\"));\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,5\"));\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,4\"));\n\n    EXPECT(waitForWindowCount(0, \"Gesture sent paste exit + enter to kitty\"), true);\n\n    EXPECT(Tests::windowCount(), 0);\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture left,3\"));\n\n    EXPECT(waitForWindowCount(1, \"Gesture spawned kitty\"), true);\n\n    EXPECT(Tests::windowCount(), 1);\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,3\"));\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"floating: 1\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture down,3\"));\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture down,3\"));\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:alt 1\"));\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture left,3\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_CONTAINS(str, \"ID 2 (2)\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,3\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_NOT_CONTAINS(str, \"ID 2 (2)\");\n    }\n\n    // check for crashes\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,3\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_NOT_CONTAINS(str, \"ID 2 (2)\");\n    }\n\n    OK(getFromSocket(\"/keyword gestures:workspace_swipe_invert 0\"));\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture right,3\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_CONTAINS(str, \"ID 2 (2)\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture left,3\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_NOT_CONTAINS(str, \"ID 2 (2)\");\n    }\n\n    OK(getFromSocket(\"/keyword gestures:workspace_swipe_invert 1\"));\n    OK(getFromSocket(\"/keyword gestures:workspace_swipe_create_new 0\"));\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture left,3\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_NOT_CONTAINS(str, \"ID 2 (2)\");\n        EXPECT_CONTAINS(str, \"ID 1 (1)\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture down,3\"));\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"floating: 0\");\n    }\n\n    OK(getFromSocket(\"/dispatch plugin:test:alt 0\"));\n\n    OK(getFromSocket(\"/dispatch plugin:test:gesture up,3\"));\n\n    EXPECT(waitForWindowCount(0, \"Gesture closed kitty\"), true);\n\n    EXPECT(Tests::windowCount(), 0);\n\n    // This test ensures that `movecursortocorner`, which expects\n    // a single-character direction argument, is parsed correctly.\n    Tests::spawnKitty();\n    OK(getFromSocket(\"/dispatch movecursortocorner 0\"));\n    const std::string cursorPos1 = getFromSocket(\"/cursorpos\");\n    OK(getFromSocket(\"/dispatch plugin:test:gesture left,4\"));\n    const std::string cursorPos2 = getFromSocket(\"/cursorpos\");\n    // The cursor should have moved because of the gesture\n    EXPECT(cursorPos1 != cursorPos2, true);\n\n    // Test that `workspace previous` works correctly after a workspace gesture.\n    {\n        OK(getFromSocket(\"/keyword gestures:workspace_swipe_invert 0\"));\n        OK(getFromSocket(\"/keyword gestures:workspace_swipe_create_new 1\"));\n        OK(getFromSocket(\"/dispatch workspace 3\"));\n\n        // Come to workspace 5 from workspace 3: 5 will remember that.\n        OK(getFromSocket(\"/dispatch workspace 5\"));\n        Tests::spawnKitty(); // Keep workspace 5 open\n\n        // Swipe from 1 to 5: 5 shall remember that.\n        OK(getFromSocket(\"/dispatch workspace 1\"));\n        OK(getFromSocket(\"/dispatch plugin:test:alt 1\"));\n        OK(getFromSocket(\"/dispatch plugin:test:gesture right,3\"));\n        OK(getFromSocket(\"/dispatch plugin:test:alt 0\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activeworkspace\"), \"ID 5 (5)\");\n\n        // Must return to 1 rather than 3\n        OK(getFromSocket(\"/dispatch workspace previous\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activeworkspace\"), \"ID 1 (1)\");\n\n        OK(getFromSocket(\"/dispatch workspace previous\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activeworkspace\"), \"ID 5 (5)\");\n\n        OK(getFromSocket(\"/dispatch workspace 1\"));\n    }\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    // reload cfg\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/groups.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <print>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nstatic bool test() {\n    NLog::log(\"{}Testing groups\", Colors::GREEN);\n\n    // test on workspace \"window\"\n    NLog::log(\"{}Dispatching workspace `groups`\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace name:groups\");\n\n    NLog::log(\"{}Spawning kittyProcA\", Colors::YELLOW);\n    auto kittyProcA = Tests::spawnKitty();\n    if (!kittyProcA) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Expecting 1 window\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 1);\n\n    // check kitty properties. One kitty should take the entire screen, minus the gaps.\n    NLog::log(\"{}Check kitty dimensions\", Colors::YELLOW);\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_COUNT_STRING(str, \"at: 22,22\", 1);\n        EXPECT_COUNT_STRING(str, \"size: 1876,1036\", 1);\n        EXPECT_COUNT_STRING(str, \"fullscreen: 0\", 1);\n    }\n\n    // group the kitty\n    NLog::log(\"{}Enable group and groupbar\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch togglegroup\"));\n    OK(getFromSocket(\"/keyword group:groupbar:enabled 1\"));\n\n    // check the height of the window now\n    NLog::log(\"{}Recheck kitty dimensions\", Colors::YELLOW);\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 22,43\");\n        EXPECT_CONTAINS(str, \"size: 1876,1015\");\n    }\n\n    // disable the groupbar for ease of testing for now\n    NLog::log(\"{}Disable groupbar\", Colors::YELLOW);\n    OK(getFromSocket(\"r/keyword group:groupbar:enabled 0\"));\n\n    // kill all\n    NLog::log(\"{}Kill windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Spawn kitty again\", Colors::YELLOW);\n    kittyProcA = Tests::spawnKitty();\n    if (!kittyProcA) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Group kitty\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch togglegroup\"));\n\n    // check the height of the window now\n    NLog::log(\"{}Check kitty dimensions 2\", Colors::YELLOW);\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876,1036\");\n    }\n\n    NLog::log(\"{}Spawn kittyProcB\", Colors::YELLOW);\n    auto kittyProcB = Tests::spawnKitty();\n    if (!kittyProcB) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Expecting 2 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 2);\n\n    size_t lastActiveKittyIdx = 0;\n\n    NLog::log(\"{}Get last active kitty id\", Colors::YELLOW);\n    try {\n        auto str           = getFromSocket(\"/activewindow\");\n        lastActiveKittyIdx = std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16);\n    } catch (...) {\n        NLog::log(\"{}Fail at getting prop\", Colors::RED);\n        ret = 1;\n    }\n\n    // test cycling through\n\n    NLog::log(\"{}Test cycling through grouped windows\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch changegroupactive f\"));\n\n    try {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(lastActiveKittyIdx != std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16), true);\n    } catch (...) {\n        NLog::log(\"{}Fail at getting prop\", Colors::RED);\n        ret = 1;\n    }\n\n    getFromSocket(\"/dispatch changegroupactive f\");\n\n    try {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(lastActiveKittyIdx, std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16));\n    } catch (...) {\n        NLog::log(\"{}Fail at getting prop\", Colors::RED);\n        ret = 1;\n    }\n\n    // test movegroupwindow: focus should follow the moved window\n    NLog::log(\"{}Test movegroupwindow focus follows window\", Colors::YELLOW);\n    try {\n        auto str              = getFromSocket(\"/activewindow\");\n        auto activeBeforeMove = std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16);\n        OK(getFromSocket(\"/dispatch movegroupwindow f\"));\n        str                  = getFromSocket(\"/activewindow\");\n        auto activeAfterMove = std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16);\n        EXPECT(activeAfterMove, activeBeforeMove);\n    } catch (...) {\n        NLog::log(\"{}Fail at getting prop\", Colors::RED);\n        ret = 1;\n    }\n\n    // and backwards\n    NLog::log(\"{}Test movegroupwindow backwards\", Colors::YELLOW);\n    try {\n        auto str              = getFromSocket(\"/activewindow\");\n        auto activeBeforeMove = std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16);\n        OK(getFromSocket(\"/dispatch movegroupwindow b\"));\n        str                  = getFromSocket(\"/activewindow\");\n        auto activeAfterMove = std::stoull(str.substr(7, str.find(\" -> \") - 7), nullptr, 16);\n        EXPECT(activeAfterMove, activeBeforeMove);\n    } catch (...) {\n        NLog::log(\"{}Fail at getting prop\", Colors::RED);\n        ret = 1;\n    }\n\n    NLog::log(\"{}Disable autogrouping\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword group:auto_group false\"));\n\n    NLog::log(\"{}Spawn kittyProcC\", Colors::YELLOW);\n    auto kittyProcC = Tests::spawnKitty();\n    if (!kittyProcC) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Expecting 3 windows 2\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 3);\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_COUNT_STRING(str, \"at: 22,22\", 2);\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus l\"));\n    OK(getFromSocket(\"/dispatch changegroupactive 1\"));\n    OK(getFromSocket(\"/keyword group:auto_group true\"));\n    OK(getFromSocket(\"/keyword group:insert_after_current false\"));\n\n    NLog::log(\"{}Spawn kittyProcD\", Colors::YELLOW);\n    auto kittyProcD = Tests::spawnKitty();\n    if (!kittyProcD) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Expecting 4 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 4);\n\n    OK(getFromSocket(\"/dispatch changegroupactive 3\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, std::format(\"pid: {}\", kittyProcD->pid()));\n    }\n\n    // kill all\n    NLog::log(\"{}Kill windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    // test movewindoworgroup: direction should be respected when extracting from group\n    NLog::log(\"{}Test movewindoworgroup respects direction out of group\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword group:groupbar:enabled 0\"));\n    {\n        auto kittyE = Tests::spawnKitty();\n        if (!kittyE) {\n            NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n            return false;\n        }\n\n        // group kitty, and new windows should be auto-grouped\n        OK(getFromSocket(\"/dispatch togglegroup\"));\n\n        auto kittyF = Tests::spawnKitty();\n        if (!kittyF) {\n            NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n            return false;\n        }\n        EXPECT(Tests::windowCount(), 2);\n\n        // both windows should be grouped at the same position\n        {\n            auto str = getFromSocket(\"/clients\");\n            EXPECT_COUNT_STRING(str, \"at: 22,22\", 2);\n        }\n\n        // move active window out of group to the right\n        NLog::log(\"{}Test movewindoworgroup r\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch movewindoworgroup r\"));\n\n        // the group should stay at x=22, the extracted window should be to the right\n        {\n            auto str = getFromSocket(\"/clients\");\n            EXPECT_COUNT_STRING(str, \"at: 22,22\", 1);\n        }\n\n        // move it back into the group\n        OK(getFromSocket(\"/dispatch moveintogroup l\"));\n\n        // move active window out of group downward\n        NLog::log(\"{}Test movewindoworgroup d\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch movewindoworgroup d\"));\n\n        // the group should stay at y=22, the extracted window should be below\n        {\n            auto str = getFromSocket(\"/clients\");\n            EXPECT_COUNT_STRING(str, \"at: 22,22\", 1);\n        }\n\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n    }\n\n    // test that we deny a floated window getting auto-grouped into a tiled group.\n    NLog::log(\"{}Test that we deny a floated window getting auto-grouped into a tiled group.\", Colors::GREEN);\n\n    OK(getFromSocket(\"/keyword windowrule[kitty-tiled]:match:class kitty_tiled\"));\n    OK(getFromSocket(\"/keyword windowrule[kitty-tiled]:tile yes\"));\n    auto kittyProcE = Tests::spawnKitty(\"kitty_tiled\");\n    if (!kittyProcE) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n    OK(getFromSocket(\"/dispatch togglegroup\"));\n\n    OK(getFromSocket(\"/keyword windowrule[kitty-floated]:match:class kitty_floated\"));\n    OK(getFromSocket(\"/keyword windowrule[kitty-floated]:float yes\"));\n    auto kittyProcF = Tests::spawnKitty(\"kitty_floated\");\n    if (!kittyProcF) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    EXPECT(Tests::windowCount(), 2);\n\n    {\n        auto clients  = getFromSocket(\"/clients\");\n        auto classPos = clients.find(\"class: kitty_floated\");\n        if (classPos == std::string::npos) {\n            NLog::log(\"{}Could not find kitty_floated in clients output\", Colors::RED);\n            ret = 1;\n        } else {\n            auto entryStart  = clients.rfind(\"Window \", classPos);\n            auto entryEnd    = clients.find(\"\\n\\n\", classPos);\n            auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);\n            EXPECT_CONTAINS(windowEntry, \"floating: 1\");\n            EXPECT_CONTAINS(windowEntry, \"grouped: 0\");\n        }\n    }\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    // Tests for grouping/merging logic\n    NLog::log(\"{}Testing locked groups w/ invade\", Colors::GREEN);\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    // Test normal, unlocked groups\n    {\n      auto winA = Tests::spawnKitty(\"unlocked\");\n      if (!winA) {\n          NLog::log(\"{}Error: unlocked kitty did not spawn\", Colors::RED);\n          return false;\n      }\n      OK(getFromSocket(\"/dispatch togglegroup\"));\n\n      auto winB = Tests::spawnKitty(\"top\");\n      if (!winB) {\n          NLog::log(\"{}Error: top kitty did not spawn\", Colors::RED);\n          return false;\n      }\n\n      // Verify it DID merge into a group\n      {\n          auto str = getFromSocket(\"/clients\");\n          EXPECT_COUNT_STRING(str, \"at: 22,22\", 2);\n      }\n    }\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    // Test locked groups\n    {\n      auto lockedWin = Tests::spawnKitty(\"locked\");\n      if (!lockedWin) {\n          NLog::log(\"{}Error: locked kitty did not spawn\", Colors::RED);\n          return false;\n      }\n      OK(getFromSocket(\"/dispatch togglegroup\"));\n      OK(getFromSocket(std::format(\"/dispatch focuswindow pid:{}\", lockedWin->pid())));\n      OK(getFromSocket(\"/dispatch lockactivegroup lock\"));\n\n      auto winB = Tests::spawnKitty(\"top\");\n      if (!winB) {\n          NLog::log(\"{}Error: top kitty did not spawn\", Colors::RED);\n          return false;\n      }\n\n      // Verify it did NOT merge into the locked group\n      {\n          auto str = getFromSocket(\"/clients\");\n          EXPECT_COUNT_STRING(str, \"at: 22,22\", 1);\n      }\n    }\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    // Test locked groups WITH invade rule\n    {\n      OK(getFromSocket(\"/keyword windowrule[locked-im]:match:class ^locked|invade$\"));\n      OK(getFromSocket(\"/keyword windowrule[locked-im]:group set always lock invade\"));\n\n      auto lockedWin = Tests::spawnKitty(\"locked\");\n      if (!lockedWin) {\n          NLog::log(\"{}Error: locked kitty did not spawn\", Colors::RED);\n          return false;\n      }\n\n      auto invadingWin = Tests::spawnKitty(\"invade\");\n      if (!invadingWin) {\n          NLog::log(\"{}Error: invading kitty did not spawn\", Colors::RED);\n          return false;\n      }\n\n      // Verify it DID merge into the locked group\n      auto str = getFromSocket(\"/clients\");\n      EXPECT_COUNT_STRING(str, \"at: 22,22\", 2);\n    }\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/hyprctl.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <cstdint>\n#include <print>\n#include <string>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nstatic std::string getCommandStdOut(std::string command) {\n    CProcess process(\"bash\", {\"-c\", command});\n    process.addEnv(\"HYPRLAND_INSTANCE_SIGNATURE\", HIS);\n    process.runSync();\n\n    const std::string& stdOut = process.stdOut();\n\n    // Remove trailing new line\n    return stdOut.substr(0, stdOut.length() - 1);\n}\n\nstatic bool testDevicesActiveLayoutIndex() {\n    NLog::log(\"{}Testing hyprctl devices active_layout_index\", Colors::GREEN);\n\n    // configure layouts\n    getFromSocket(\"/keyword input:kb_layout us,pl,ua\");\n\n    for (uint8_t i = 0; i < 3; i++) {\n        // set layout\n        getFromSocket(\"/switchxkblayout all \" + std::to_string(i));\n        std::string devicesJson = getFromSocket(\"j/devices\");\n        std::string expected    = R\"(\"active_layout_index\": )\" + std::to_string(i);\n        // check layout index\n        EXPECT_CONTAINS(devicesJson, expected);\n    }\n\n    return true;\n}\n\nstatic bool testGetprop() {\n    NLog::log(\"{}Testing hyprctl getprop\", Colors::GREEN);\n    if (!Tests::spawnKitty()) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    // animation\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty animation\"), \"(unset)\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty animation -j\"), R\"({\"animation\": \"\"})\");\n    getFromSocket(\"/dispatch setprop class:kitty animation teststyle\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty animation\"), \"teststyle\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty animation -j\"), R\"({\"animation\": \"teststyle\"})\");\n\n    // max_size\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty max_size\"), \"inf inf\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty max_size -j\"), R\"({\"max_size\": [null,null]})\");\n    getFromSocket(\"/dispatch setprop class:kitty max_size 200 150\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty max_size\"), \"200 150\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty max_size -j\"), R\"({\"max_size\": [200,150]})\");\n\n    // min_size\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty min_size\"), \"20 20\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty min_size -j\"), R\"({\"min_size\": [20,20]})\");\n    getFromSocket(\"/dispatch setprop class:kitty min_size 100 50\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty min_size\"), \"100 50\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty min_size -j\"), R\"({\"min_size\": [100,50]})\");\n\n    // expr-based min/max _size\n    getFromSocket(\"/dispatch setfloating class:kitty\");                 // need to set floating for tests below\n    getFromSocket(\"/dispatch setprop class:kitty max_size 90+10 25*2\"); // set max to the same as min above, forcing window to 100*50\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty max_size\"), \"100 50\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty max_size -j\"), R\"({\"max_size\": [100,50]})\");\n    getFromSocket(\"/dispatch setprop class:kitty min_size window_w*0.5 window_h-10\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty min_size\"), \"50 40\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty min_size -j\"), R\"({\"min_size\": [50,40]})\");\n    getFromSocket(\"/dispatch settiled class:kitty\"); // go back to tiled for consistency\n\n    // opacity\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity\"), \"1\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity -j\"), R\"({\"opacity\": 1})\");\n    getFromSocket(\"/dispatch setprop class:kitty opacity 0.3\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity\"), \"0.3\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity -j\"), R\"({\"opacity\": 0.3})\");\n\n    // opacity_inactive\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive\"), \"1\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive -j\"), R\"({\"opacity_inactive\": 1})\");\n    getFromSocket(\"/dispatch setprop class:kitty opacity_inactive 0.5\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive\"), \"0.5\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive -j\"), R\"({\"opacity_inactive\": 0.5})\");\n\n    // opacity_fullscreen\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen\"), \"1\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen -j\"), R\"({\"opacity_fullscreen\": 1})\");\n    getFromSocket(\"/dispatch setprop class:kitty opacity_fullscreen 0.75\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen\"), \"0.75\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen -j\"), R\"({\"opacity_fullscreen\": 0.75})\");\n\n    // opacity_override\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_override\"), \"false\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_override -j\"), R\"({\"opacity_override\": false})\");\n    getFromSocket(\"/dispatch setprop class:kitty opacity_override true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_override\"), \"true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_override -j\"), R\"({\"opacity_override\": true})\");\n\n    // opacity_inactive_override\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive_override\"), \"false\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive_override -j\"), R\"({\"opacity_inactive_override\": false})\");\n    getFromSocket(\"/dispatch setprop class:kitty opacity_inactive_override true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive_override\"), \"true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_inactive_override -j\"), R\"({\"opacity_inactive_override\": true})\");\n\n    // opacity_fullscreen_override\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen_override\"), \"false\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen_override -j\"), R\"({\"opacity_fullscreen_override\": false})\");\n    getFromSocket(\"/dispatch setprop class:kitty opacity_fullscreen_override true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen_override\"), \"true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty opacity_fullscreen_override -j\"), R\"({\"opacity_fullscreen_override\": true})\");\n\n    // active_border_color\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty active_border_color\"), \"ee33ccff ee00ff99 45deg\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty active_border_color -j\"), R\"({\"active_border_color\": \"ee33ccff ee00ff99 45deg\"})\");\n    getFromSocket(\"/dispatch setprop class:kitty active_border_color rgb(abcdef)\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty active_border_color\"), \"ffabcdef 0deg\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty active_border_color -j\"), R\"({\"active_border_color\": \"ffabcdef 0deg\"})\");\n\n    // bool window properties\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty allows_input\"), \"false\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty allows_input -j\"), R\"({\"allows_input\": false})\");\n    getFromSocket(\"/dispatch setprop class:kitty allows_input true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty allows_input\"), \"true\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty allows_input -j\"), R\"({\"allows_input\": true})\");\n\n    // int window properties\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding\"), \"10\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding -j\"), R\"({\"rounding\": 10})\");\n    getFromSocket(\"/dispatch setprop class:kitty rounding 4\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding\"), \"4\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding -j\"), R\"({\"rounding\": 4})\");\n\n    // float window properties\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding_power\"), \"2\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding_power -j\"), R\"({\"rounding_power\": 2})\");\n    getFromSocket(\"/dispatch setprop class:kitty rounding_power 1.25\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding_power\"), \"1.25\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty rounding_power -j\"), R\"({\"rounding_power\": 1.25})\");\n\n    // errors\n    EXPECT(getCommandStdOut(\"hyprctl getprop\"), \"not enough args\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty\"), \"not enough args\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:nonexistantclass animation\"), \"window not found\");\n    EXPECT(getCommandStdOut(\"hyprctl getprop class:kitty nonexistantprop\"), \"prop not found\");\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return true;\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing hyprctl\", Colors::GREEN);\n\n    {\n        NLog::log(\"{}Testing hyprctl descriptions for any json errors\", Colors::GREEN);\n        CProcess jqProc(\"bash\", {\"-c\", \"hyprctl descriptions | jq\"});\n        jqProc.addEnv(\"HYPRLAND_INSTANCE_SIGNATURE\", HIS);\n        jqProc.runSync();\n        EXPECT(jqProc.exitCode(), 0);\n    }\n\n    testGetprop();\n    testDevicesActiveLayoutIndex();\n    getFromSocket(\"/reload\");\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/main/keybinds.cpp",
    "content": "#include <filesystem>\n#include <linux/input-event-codes.h>\n#include <thread>\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\nstatic int         ret      = 0;\nstatic std::string flagFile = \"/tmp/hyprtester-keybinds.txt\";\n\n// Because i don't feel like changing someone elses code.\nenum eKeyboardModifierIndex : uint8_t {\n    MOD_SHIFT = 1,\n    MOD_CAPS,\n    MOD_CTRL,\n    MOD_ALT,\n    MOD_MOD2,\n    MOD_MOD3,\n    MOD_META,\n    MOD_MOD5\n};\n\nstatic void clearFlag() {\n    std::filesystem::remove(flagFile);\n}\n\nstatic bool checkFlag() {\n    bool exists = std::filesystem::exists(flagFile);\n    clearFlag();\n    return exists;\n}\n\nstatic bool attemptCheckFlag(int attempts, int intervalMs) {\n    for (int i = 0; i < attempts; i++) {\n        if (checkFlag())\n            return true;\n\n        std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));\n    }\n\n    return false;\n}\n\nstatic std::string readKittyOutput() {\n    std::string output = Tests::execAndGet(\"kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all\");\n    // chop off shell prompt\n    std::size_t pos = output.rfind(\"$\");\n    if (pos != std::string::npos) {\n        pos += 1;\n        if (pos < output.size())\n            output.erase(0, pos);\n    }\n    // NLog::log(\"Kitty output: '{}'\", output);\n    return output;\n}\n\nstatic void awaitKittyPrompt() {\n    // wait until we see the shell prompt, meaning it's ready for test inputs\n    for (int i = 0; i < 10; i++) {\n        std::string output = Tests::execAndGet(\"kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all\");\n        if (output.rfind(\"$\") == std::string::npos) {\n            std::this_thread::sleep_for(std::chrono::milliseconds(200));\n            continue;\n        }\n        return;\n    }\n    NLog::log(\"{}Error: timed out waiting for kitty prompt\", Colors::RED);\n}\n\nstatic CUniquePointer<CProcess> spawnRemoteControlKitty() {\n    auto kittyProc = Tests::spawnKitty(\"keybinds_test\", {\"-o\", \"allow_remote_control=yes\", \"--listen-on\", \"unix:/tmp/hyprtester-kitty.sock\", \"--config\", \"NONE\", \"/bin/sh\"});\n    // wait a bit to ensure shell prompt is sent, we are going to read the text after it\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    if (kittyProc)\n        awaitKittyPrompt();\n    return kittyProc;\n}\n\nstatic void testBind() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bind SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // await flag\n    EXPECT(attemptCheckFlag(20, 50), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testBindKey() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bind ,Y,exec,touch \" + flagFile), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,0,29\"));\n    // await flag\n    EXPECT(attemptCheckFlag(20, 50), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind ,Y\"), \"ok\");\n}\n\nstatic void testLongPress() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindo SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // check no flag on short press\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    EXPECT(checkFlag(), false);\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testKeyLongPress() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindo ,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,0,29\"));\n    // check no flag on short press\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    EXPECT(checkFlag(), false);\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind ,Y\"), \"ok\");\n}\n\nstatic void testLongPressRelease() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindo SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // check no flag on short press\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    EXPECT(checkFlag(), false);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testLongPressOnlyKeyRelease() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindo SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // check no flag on short press\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    EXPECT(checkFlag(), false);\n    // release key, keep modifier\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,7,29\"));\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), false);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testRepeat() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword binde SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // await flag\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // check that it continues repeating\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testKeyRepeat() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword binde ,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,0,29\"));\n    // await flag\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    EXPECT(checkFlag(), true);\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // check that it continues repeating\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind ,Y\"), \"ok\");\n}\n\nstatic void testRepeatRelease() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword binde SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // await flag\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    EXPECT(checkFlag(), true);\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    clearFlag();\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), false);\n    // check that it is not repeating\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testRepeatOnlyKeyRelease() {\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword binde SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // await flag\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), true);\n    // release key, keep modifier\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,7,29\"));\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    clearFlag();\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), false);\n    // check that it is not repeating\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    EXPECT(checkFlag(), false);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testShortcutBind() {\n    auto kittyProc = spawnRemoteControlKitty();\n    if (!kittyProc) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        ret = 1;\n        return;\n    }\n    EXPECT(getFromSocket(\"/dispatch focuswindow class:keybinds_test\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword bind SUPER,Y,sendshortcut,,q,\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // release keybind\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    const std::string output = readKittyOutput();\n    EXPECT_COUNT_STRING(output, \"y\", 0);\n    EXPECT_COUNT_STRING(output, \"q\", 1);\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n    Tests::killAllWindows();\n}\n\nstatic void testShortcutBindKey() {\n    auto kittyProc = spawnRemoteControlKitty();\n    if (!kittyProc) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        ret = 1;\n        return;\n    }\n    EXPECT(getFromSocket(\"/dispatch focuswindow class:keybinds_test\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword bind ,Y,sendshortcut,,q,\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,0,29\"));\n    // release keybind\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    std::this_thread::sleep_for(std::chrono::milliseconds(50));\n    const std::string output = readKittyOutput();\n    EXPECT_COUNT_STRING(output, \"y\", 0);\n    // disabled: doesn't work in CI\n    // EXPECT_COUNT_STRING(output, \"q\", 1);\n    EXPECT(getFromSocket(\"/keyword unbind ,Y\"), \"ok\");\n    Tests::killAllWindows();\n}\n\nstatic void testShortcutLongPress() {\n    auto kittyProc = spawnRemoteControlKitty();\n    if (!kittyProc) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        ret = 1;\n        return;\n    }\n    EXPECT(getFromSocket(\"/dispatch focuswindow class:keybinds_test\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword bindo SUPER,Y,sendshortcut,,q,\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_rate 10\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    const std::string output = readKittyOutput();\n    int               yCount = Tests::countOccurrences(output, \"y\");\n    // sometimes 1, sometimes 2, not sure why\n    // keybind press sends 1 y immediately\n    // then repeat triggers, sending 1 y\n    // final release stop repeats, and shouldn't send any more\n    EXPECT(true, yCount == 1 || yCount == 2);\n    EXPECT_COUNT_STRING(output, \"q\", 1);\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n    Tests::killAllWindows();\n}\n\nstatic void testShortcutLongPressKeyRelease() {\n    auto kittyProc = spawnRemoteControlKitty();\n    if (!kittyProc) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        ret = 1;\n        return;\n    }\n    EXPECT(getFromSocket(\"/dispatch focuswindow class:keybinds_test\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword bindo SUPER,Y,sendshortcut,,q,\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 100\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_rate 10\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n    // release key, keep modifier\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,7,29\"));\n    // await repeat delay\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    const std::string output = readKittyOutput();\n    // disabled: doesn't work on CI\n    // EXPECT_COUNT_STRING(output, \"y\", 1);\n    EXPECT_COUNT_STRING(output, \"q\", 0);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n    Tests::killAllWindows();\n}\n\nstatic void testShortcutRepeat() {\n    auto kittyProc = spawnRemoteControlKitty();\n    if (!kittyProc) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        ret = 1;\n        return;\n    }\n    EXPECT(getFromSocket(\"/dispatch focuswindow class:keybinds_test\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword binde SUPER,Y,sendshortcut,,q,\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_rate 5\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 200\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    // await repeat\n    std::this_thread::sleep_for(std::chrono::milliseconds(210));\n    // release keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    std::this_thread::sleep_for(std::chrono::milliseconds(450));\n    const std::string output = readKittyOutput();\n    EXPECT_COUNT_STRING(output, \"y\", 0);\n    int qCount = Tests::countOccurrences(output, \"q\");\n    // sometimes 2, sometimes 3, not sure why\n    // keybind press sends 1 q immediately\n    // then repeat triggers, sending 1 q\n    // final release stop repeats, and shouldn't send any more\n    EXPECT(true, qCount == 2 || qCount == 3);\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n    Tests::killAllWindows();\n}\n\nstatic void testShortcutRepeatKeyRelease() {\n    auto kittyProc = spawnRemoteControlKitty();\n    if (!kittyProc) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        ret = 1;\n        return;\n    }\n    EXPECT(getFromSocket(\"/dispatch focuswindow class:keybinds_test\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword binde SUPER,Y,sendshortcut,,q,\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_rate 5\"), \"ok\");\n    EXPECT(getFromSocket(\"/keyword input:repeat_delay 200\"), \"ok\");\n    // press keybind\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    std::this_thread::sleep_for(std::chrono::milliseconds(210));\n    // release key, keep modifier\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,7,29\"));\n    // if repeat was still active, we'd get 2 more q's here\n    std::this_thread::sleep_for(std::chrono::milliseconds(450));\n    // release modifier\n    const std::string output = readKittyOutput();\n    EXPECT_COUNT_STRING(output, \"y\", 0);\n    int qCount = Tests::countOccurrences(output, \"q\");\n    // sometimes 2, sometimes 3, not sure why\n    // keybind press sends 1 q immediately\n    // then repeat triggers, sending 1 q\n    // final release stop repeats, and shouldn't send any more\n    EXPECT(true, qCount == 2 || qCount == 3);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n    Tests::killAllWindows();\n}\n\nstatic void testSubmap() {\n    const auto press = [](const uint32_t key, const uint32_t mod = 0) {\n        // +8 because udev -> XKB keycode.\n        getFromSocket(\"/dispatch plugin:test:keybind 1,\" + std::to_string(mod) + \",\" + std::to_string(key + 8));\n        getFromSocket(\"/dispatch plugin:test:keybind 0,\" + std::to_string(mod) + \",\" + std::to_string(key + 8));\n    };\n\n    NLog::log(\"{}Testing submaps\", Colors::GREEN);\n    // submap 1 no resets\n    press(KEY_U, MOD_META);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap1\");\n    press(KEY_O);\n    Tests::waitUntilWindowsN(1);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap1\");\n    // submap 2 resets to submap 1\n    press(KEY_U);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap2\");\n    press(KEY_O);\n    Tests::waitUntilWindowsN(2);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap1\");\n    // submap 3 resets to default\n    press(KEY_I);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap3\");\n    press(KEY_O);\n    Tests::waitUntilWindowsN(3);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"default\");\n    // submap 1 reset via keybind\n    press(KEY_U, MOD_META);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap1\");\n    press(KEY_P);\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"default\");\n\n    Tests::killAllWindows();\n}\n\nstatic void testBindsAfterScroll() {\n    NLog::log(\"{}Testing binds after scroll\", Colors::GREEN);\n\n    clearFlag();\n    OK(getFromSocket(\"/keyword binds Alt_R,w,exec,touch \" + flagFile));\n\n    // press keybind before scroll\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,0,108\")); // Alt_R press\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,4,25\"));  // w press\n    EXPECT(attemptCheckFlag(20, 50), true);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,4,25\"));  // w release\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,108\")); // Alt_R release\n\n    // scroll\n    OK(getFromSocket(\"/dispatch plugin:test:scroll 120\"));\n    OK(getFromSocket(\"/dispatch plugin:test:scroll -120\"));\n    OK(getFromSocket(\"/dispatch plugin:test:scroll 120\"));\n\n    // press keybind after scroll\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,0,108\")); // Alt_R press\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,4,25\"));  // w press\n    EXPECT(attemptCheckFlag(20, 50), true);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,4,25\"));  // w release\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,108\")); // Alt_R release\n\n    clearFlag();\n    OK(getFromSocket(\"/keyword unbind Alt_R,w\"));\n}\n\nstatic void testSubmapUniversal() {\n    NLog::log(\"{}Testing submap universal\", Colors::GREEN);\n\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindu SUPER,Y,exec,touch \" + flagFile), \"ok\");\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"default\");\n\n    // keybind works on default submap\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,7,29\"));\n    EXPECT(attemptCheckFlag(30, 5), true);\n\n    // keybind works on submap1\n    getFromSocket(\"/dispatch plugin:test:keybind 1,7,30\");\n    getFromSocket(\"/dispatch plugin:test:keybind 0,7,30\");\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"submap1\");\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,7,29\"));\n    EXPECT(attemptCheckFlag(30, 5), true);\n\n    // reset to default submap\n    getFromSocket(\"/dispatch plugin:test:keybind 1,0,33\");\n    getFromSocket(\"/dispatch plugin:test:keybind 0,0,33\");\n    EXPECT_CONTAINS(getFromSocket(\"/submap\"), \"default\");\n\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic void testPerDeviceKeybind() {\n    NLog::log(\"{}Testing per-device binds\", Colors::GREEN);\n\n    // Inclusive\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindk SUPER,Y,test-keyboard-1,exec,touch \" + flagFile), \"ok\");\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    EXPECT(attemptCheckFlag(20, 50), true);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n\n    // Exclusive\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword bindk SUPER,Y,!test-keyboard-1,exec,touch \" + flagFile), \"ok\");\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    EXPECT(attemptCheckFlag(20, 50), false);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n\n    // With description\n    EXPECT(checkFlag(), false);\n    EXPECT(getFromSocket(\"/keyword binddk SUPER,Y,test-keyboard-1,test description,exec,touch \" + flagFile), \"ok\");\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 1,7,29\"));\n    EXPECT(attemptCheckFlag(20, 50), true);\n    OK(getFromSocket(\"/dispatch plugin:test:keybind 0,0,29\"));\n    EXPECT(getFromSocket(\"/keyword unbind SUPER,Y\"), \"ok\");\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing keybinds\", Colors::GREEN);\n\n    clearFlag();\n\n    testBind();\n    testBindKey();\n    testLongPress();\n    testKeyLongPress();\n    testLongPressRelease();\n    testLongPressOnlyKeyRelease();\n    testRepeat();\n    testKeyRepeat();\n    testRepeatRelease();\n    testRepeatOnlyKeyRelease();\n    testShortcutBind();\n    testShortcutBindKey();\n    testShortcutLongPress();\n    testShortcutLongPressKeyRelease();\n    testShortcutRepeat();\n    testShortcutRepeatKeyRelease();\n    testSubmap();\n    testSubmapUniversal();\n    testBindsAfterScroll();\n    testPerDeviceKeybind();\n\n    clearFlag();\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/layer.cpp",
    "content": "#include \"../../Log.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\nstatic bool spawnLayer(const std::string& namespace_) {\n    NLog::log(\"{}Spawning kitty layer {}\", Colors::YELLOW, namespace_);\n    if (!Tests::spawnLayerKitty(namespace_)) {\n        NLog::log(\"{}Error: {} layer did not spawn\", Colors::RED, namespace_);\n        return false;\n    }\n    return true;\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing plugin layerrules\", Colors::GREEN);\n\n    if (!spawnLayer(\"rule-layer\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch plugin:test:add_layer_rule\"));\n    OK(getFromSocket(\"/reload\"));\n\n    OK(getFromSocket(\"/keyword layerrule match:namespace rule-layer, plugin_rule effect\"));\n\n    if (!spawnLayer(\"rule-layer\"))\n        return false;\n\n    if (!spawnLayer(\"norule-layer\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch plugin:test:check_layer_rule\"));\n\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Killing all layers\", Colors::YELLOW);\n    Tests::killAllLayers();\n\n    NLog::log(\"{}Expecting 0 layers\", Colors::YELLOW);\n    EXPECT(Tests::layerCount(), 0);\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/layout.cpp",
    "content": "#include \"../shared.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"tests.hpp\"\n\nstatic int  ret = 0;\n\nstatic void swar() {\n    OK(getFromSocket(\"/keyword layout:single_window_aspect_ratio 1 1\"));\n\n    Tests::spawnKitty();\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 442,22\");\n        EXPECT_CONTAINS(str, \"size: 1036,1036\");\n    }\n\n    Tests::spawnKitty();\n\n    OK(getFromSocket(\"/dispatch killwindow activewindow\"));\n\n    Tests::waitUntilWindowsN(1);\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 442,22\");\n        EXPECT_CONTAINS(str, \"size: 1036,1036\");\n    }\n\n    // don't use swar on maximized\n    OK(getFromSocket(\"/dispatch fullscreen 1\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876,1036\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\n// Don't crash when focus after global geometry changes\nstatic void testCrashOnGeomUpdate() {\n    Tests::spawnKitty();\n    Tests::spawnKitty();\n    Tests::spawnKitty();\n\n    // move the layout\n    OK(getFromSocket(\"/keyword monitor HEADLESS-2,1920x1080@60,1000x0,1\"));\n\n    // shouldnt crash\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\n// Test if size + pos is preserved after fs cycle\nstatic void testPosPreserve() {\n    Tests::spawnKitty();\n\n    OK(getFromSocket(\"/dispatch setfloating class:kitty\"));\n    OK(getFromSocket(\"/dispatch resizewindowpixel exact 1337 69, class:kitty\"));\n    OK(getFromSocket(\"/dispatch movewindowpixel exact 420 420, class:kitty\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 420,420\");\n        EXPECT_CONTAINS(str, \"size: 1337,69\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen\"));\n    OK(getFromSocket(\"/dispatch fullscreen\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"size: 1337,69\");\n    }\n\n    OK(getFromSocket(\"/dispatch movewindow r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 581,420\");\n        EXPECT_CONTAINS(str, \"size: 1337,69\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen\"));\n    OK(getFromSocket(\"/dispatch fullscreen\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 581,420\");\n        EXPECT_CONTAINS(str, \"size: 1337,69\");\n    }\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing layout generic\", Colors::GREEN);\n\n    // setup\n    OK(getFromSocket(\"/dispatch workspace 10\"));\n\n    // test\n    NLog::log(\"{}Testing `single_window_aspect_ratio`\", Colors::GREEN);\n    swar();\n\n    testCrashOnGeomUpdate();\n    testPosPreserve();\n\n    // clean up\n    NLog::log(\"Cleaning up\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/main/master.cpp",
    "content": "#include \"../shared.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"tests.hpp\"\n\nstatic int ret = 0;\n\n// reqs 1 master 3 slaves\nstatic void testOrientations() {\n    OK(getFromSocket(\"/keyword master:orientation top\"));\n\n    // top\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876\");\n    }\n\n    // cycle = top, right, bottom, center, left\n\n    // right\n    OK(getFromSocket(\"/dispatch layoutmsg orientationnext\"));\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 873,22\");\n        EXPECT_CONTAINS(str, \"size: 1025,1036\");\n    }\n\n    // bottom\n    OK(getFromSocket(\"/dispatch layoutmsg orientationnext\"));\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,495\");\n        EXPECT_CONTAINS(str, \"size: 1876\");\n    }\n\n    // center\n    OK(getFromSocket(\"/dispatch layoutmsg orientationnext\"));\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 450,22\");\n        EXPECT_CONTAINS(str, \"size: 1020,1036\");\n    }\n\n    // left\n    OK(getFromSocket(\"/dispatch layoutmsg orientationnext\"));\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1025,1036\");\n    }\n}\n\nstatic void focusMasterPrevious() {\n    // setup\n    NLog::log(\"{}Spawning 1 master and 3 slave windows\", Colors::YELLOW);\n    // order of windows set according to new_status = master (set in test.conf)\n    for (auto const& win : {\"slave1\", \"slave2\", \"slave3\", \"master\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n    NLog::log(\"{}Ensuring focus is on master before testing\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch layoutmsg focusmaster master\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: master\");\n\n    // test\n    NLog::log(\"{}Testing fallback to focusmaster auto\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/dispatch layoutmsg focusmaster previous\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: slave1\");\n\n    NLog::log(\"{}Testing focusing from slave to master\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/dispatch layoutmsg cyclenext noloop\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: slave2\");\n    OK(getFromSocket(\"/dispatch layoutmsg focusmaster previous\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: master\");\n\n    NLog::log(\"{}Testing focusing on previous window\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/dispatch layoutmsg focusmaster previous\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: slave2\");\n\n    NLog::log(\"{}Testing focusing back to master\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/dispatch layoutmsg focusmaster previous\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: master\");\n\n    testOrientations();\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic void testFsBehavior() {\n    // Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen\n    // check that it doesn't.\n\n    for (auto const& win : {\"master\", \"slave1\", \"slave2\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:master\"));\n    OK(getFromSocket(\"/dispatch fullscreen 1\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876,1036\");\n        EXPECT_CONTAINS(str, \"class: master\");\n    }\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 1\"));\n\n    Tests::spawnKitty(\"new_master\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876,1036\");\n        EXPECT_CONTAINS(str, \"class: new_master\");\n        EXPECT_CONTAINS(str, \"fullscreen: 1\");\n    }\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 0\"));\n\n    Tests::spawnKitty(\"ignored\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"at: 22,22\");\n        EXPECT_CONTAINS(str, \"size: 1876,1036\");\n        EXPECT_CONTAINS(str, \"class: new_master\");\n        EXPECT_CONTAINS(str, \"fullscreen: 1\");\n    }\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 2\"));\n\n    Tests::spawnKitty(\"vaxwashere\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: vaxwashere\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n    }\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing Master layout\", Colors::GREEN);\n\n    // setup\n    OK(getFromSocket(\"/dispatch workspace name:master\"));\n    OK(getFromSocket(\"/keyword general:layout master\"));\n\n    // test\n    NLog::log(\"{}Testing `focusmaster previous` layoutmsg\", Colors::GREEN);\n    focusMasterPrevious();\n\n    NLog::log(\"{}Testing fs behavior\", Colors::GREEN);\n    testFsBehavior();\n\n    // clean up\n    NLog::log(\"Cleaning up\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/main/misc.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <print>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\n// Uncomment once test vm can run hyprland-dialog\n// static void testAnrDialogs() {\n//     NLog::log(\"{}Testing ANR dialogs\", Colors::YELLOW);\n//\n//     OK(getFromSocket(\"/keyword misc:enable_anr_dialog true\"));\n//     OK(getFromSocket(\"/keyword misc:anr_missed_pings 1\"));\n//\n//     NLog::log(\"{}ANR dialog: regular workspaces\", Colors::YELLOW);\n//     {\n//         OK(getFromSocket(\"/dispatch workspace 2\"));\n//\n//         auto kitty = Tests::spawnKitty(\"bad_kitty\");\n//\n//         if (!kitty) {\n//             ret = 1;\n//             return;\n//         }\n//\n//         {\n//             auto str = getFromSocket(\"/activewindow\");\n//             EXPECT_CONTAINS(str, \"workspace: 2\");\n//         }\n//\n//         OK(getFromSocket(\"/dispatch workspace 1\"));\n//\n//         ::kill(kitty->pid(), SIGSTOP);\n//         Tests::waitUntilWindowsN(2);\n//\n//         {\n//             auto str = getFromSocket(\"/activeworkspace\");\n//             EXPECT_CONTAINS(str, \"windows: 0\");\n//         }\n//\n//         {\n//             OK(getFromSocket(\"/dispatch focuswindow class:hyprland-dialog\"))\n//             auto str = getFromSocket(\"/activewindow\");\n//             EXPECT_CONTAINS(str, \"workspace: 2\");\n//         }\n//     }\n//\n//     Tests::killAllWindows();\n//\n//     NLog::log(\"{}ANR dialog: named workspaces\", Colors::YELLOW);\n//     {\n//         OK(getFromSocket(\"/dispatch workspace name:yummy\"));\n//\n//         auto kitty = Tests::spawnKitty(\"bad_kitty\");\n//\n//         if (!kitty) {\n//             ret = 1;\n//             return;\n//         }\n//\n//         {\n//             auto str = getFromSocket(\"/activewindow\");\n//             EXPECT_CONTAINS(str, \"yummy\");\n//         }\n//\n//         OK(getFromSocket(\"/dispatch workspace 1\"));\n//\n//         ::kill(kitty->pid(), SIGSTOP);\n//         Tests::waitUntilWindowsN(2);\n//\n//         {\n//             auto str = getFromSocket(\"/activeworkspace\");\n//             EXPECT_CONTAINS(str, \"windows: 0\");\n//         }\n//\n//         {\n//             OK(getFromSocket(\"/dispatch focuswindow class:hyprland-dialog\"))\n//             auto str = getFromSocket(\"/activewindow\");\n//             EXPECT_CONTAINS(str, \"yummy\");\n//         }\n//     }\n//\n//     Tests::killAllWindows();\n//\n//     NLog::log(\"{}ANR dialog: special workspaces\", Colors::YELLOW);\n//     {\n//         OK(getFromSocket(\"/dispatch workspace special:apple\"));\n//\n//         auto kitty = Tests::spawnKitty(\"bad_kitty\");\n//\n//         if (!kitty) {\n//             ret = 1;\n//             return;\n//         }\n//\n//         {\n//             auto str = getFromSocket(\"/activewindow\");\n//             EXPECT_CONTAINS(str, \"special:apple\");\n//         }\n//\n//         OK(getFromSocket(\"/dispatch togglespecialworkspace apple\"));\n//         OK(getFromSocket(\"/dispatch workspace 1\"));\n//\n//         ::kill(kitty->pid(), SIGSTOP);\n//         Tests::waitUntilWindowsN(2);\n//\n//         {\n//             auto str = getFromSocket(\"/activeworkspace\");\n//             EXPECT_CONTAINS(str, \"windows: 0\");\n//         }\n//\n//         {\n//             OK(getFromSocket(\"/dispatch focuswindow class:hyprland-dialog\"))\n//             auto str = getFromSocket(\"/activewindow\");\n//             EXPECT_CONTAINS(str, \"special:apple\");\n//         }\n//     }\n//\n//     OK(getFromSocket(\"/reload\"));\n//     Tests::killAllWindows();\n// }\n\nstatic bool test() {\n    NLog::log(\"{}Testing config: misc:\", Colors::GREEN);\n\n    NLog::log(\"{}Testing close_special_on_empty\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword misc:close_special_on_empty false\"));\n    OK(getFromSocket(\"/dispatch workspace special:test\"));\n\n    Tests::spawnKitty();\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"special workspace: -\");\n    }\n\n    Tests::killAllWindows();\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"special workspace: -\");\n    }\n\n    Tests::spawnKitty();\n\n    OK(getFromSocket(\"/keyword misc:close_special_on_empty true\"));\n\n    Tests::killAllWindows();\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_NOT_CONTAINS(str, \"special workspace: -\");\n    }\n\n    NLog::log(\"{}Testing new_window_takes_over_fullscreen\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 0\"));\n\n    Tests::spawnKitty(\"kitty_A\");\n\n    OK(getFromSocket(\"/dispatch fullscreen 0\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n        EXPECT_CONTAINS(str, \"kitty_A\");\n    }\n\n    Tests::spawnKitty(\"kitty_B\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n        EXPECT_CONTAINS(str, \"kitty_A\");\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n\n    {\n        // should be ignored as per focus_under_fullscreen 0\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n        EXPECT_CONTAINS(str, \"kitty_A\");\n    }\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 1\"));\n\n    Tests::spawnKitty(\"kitty_C\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n        EXPECT_CONTAINS(str, \"kitty_C\");\n    }\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 2\"));\n\n    Tests::spawnKitty(\"kitty_D\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 0\");\n        EXPECT_CONTAINS(str, \"kitty_D\");\n    }\n\n    OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 0\"));\n\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Testing exit_window_retains_fullscreen\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword misc:exit_window_retains_fullscreen false\"));\n\n    Tests::spawnKitty(\"kitty_A\");\n    Tests::spawnKitty(\"kitty_B\");\n\n    OK(getFromSocket(\"/dispatch fullscreen 0\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n    }\n\n    OK(getFromSocket(\"/dispatch killwindow activewindow\"));\n    Tests::waitUntilWindowsN(1);\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 0\");\n    }\n\n    Tests::spawnKitty(\"kitty_B\");\n    OK(getFromSocket(\"/dispatch fullscreen 0\"));\n    OK(getFromSocket(\"/keyword misc:exit_window_retains_fullscreen true\"));\n\n    OK(getFromSocket(\"/dispatch killwindow activewindow\"));\n    Tests::waitUntilWindowsN(1);\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n    }\n\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Testing fullscreen and fullscreenstate dispatcher\", Colors::YELLOW);\n\n    Tests::spawnKitty(\"kitty_A\");\n    Tests::spawnKitty(\"kitty_B\");\n\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n    OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen 0 unset\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 0\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen 1 toggle\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 1\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 1\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen 1 toggle\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 0\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreenstate 3 3 set\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 3\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 3\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreenstate 3 3 set\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 3\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 3\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreenstate 2 2 set\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreenstate 2 2 set\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreenstate 2 2 toggle\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 0\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 0\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreenstate 2 2 toggle\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n        EXPECT_CONTAINS(str, \"fullscreenClient: 2\");\n    }\n\n    // Ensure that the process autostarted in the config does not\n    // become a zombie even if it terminates very quickly.\n    EXPECT(Tests::execAndGet(\"pgrep -f 'sleep 0'\").empty(), true);\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/main/persistent.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <print>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nstatic bool test() {\n    NLog::log(\"{}Testing persistent workspaces\", Colors::GREEN);\n\n    EXPECT(Tests::windowCount(), 0);\n\n    // test on workspace \"window\"\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace 1\"); // no OK: we might be on 1 already\n\n    OK(getFromSocket(\"/keyword workspace 5, monitor:HEADLESS-2, persistent:1\"));\n    OK(getFromSocket(\"/keyword workspace 6, monitor:HEADLESS-PERSISTENT-TEST, persistent:1\"));\n    OK(getFromSocket(\"/keyword workspace name:PERSIST, monitor:HEADLESS-PERSISTENT-TEST, persistent:1\"));\n    OK(getFromSocket(\"/keyword workspace name:PERSIST-2, monitor:HEADLESS-PERSISTENT-TEST, persistent:1\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_CONTAINS(str, \"ID 5 (5)\");\n        EXPECT_COUNT_STRING(str, \"workspace ID \", 2);\n    }\n\n    OK(getFromSocket(\"/output create headless HEADLESS-PERSISTENT-TEST\"));\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"HEADLESS-PERSISTENT-TEST\");\n    }\n\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-PERSISTENT-TEST\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_CONTAINS(str, \"ID 2 (2)\"); // this should be automatically generated by hl\n        EXPECT_CONTAINS(str, \"ID 5 (5)\");\n        EXPECT_CONTAINS(str, \"ID 6 (6)\");\n        EXPECT_CONTAINS(str, \"(PERSIST) on monitor\");\n        EXPECT_CONTAINS(str, \"(PERSIST-2) on monitor\");\n        EXPECT_COUNT_STRING(str, \"workspace ID \", 6);\n    }\n\n    OK(getFromSocket(\"/reload\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_NOT_CONTAINS(str, \"ID 5 (5)\");\n        EXPECT_NOT_CONTAINS(str, \"ID 6 (6)\");\n        EXPECT_NOT_CONTAINS(str, \"(PERSIST) on monitor\");\n        EXPECT_COUNT_STRING(str, \"workspace ID \", 2);\n    }\n\n    OK(getFromSocket(\"/output remove HEADLESS-PERSISTENT-TEST\"));\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    // reload cfg\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/scroll.cpp",
    "content": "#include \"../shared.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"tests.hpp\"\n\nstatic int  ret = 0;\n\nstatic void testFocusCycling() {\n    for (auto const& win : {\"a\", \"b\", \"c\", \"d\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: c\");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: d\");\n    }\n\n    OK(getFromSocket(\"/dispatch movewindow l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: d\");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus u\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: c\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic void testFocusWrapping() {\n    for (auto const& win : {\"a\", \"b\", \"c\", \"d\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    // set wrap_focus to true\n    OK(getFromSocket(\"/keyword scrolling:wrap_focus true\"));\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n\n    OK(getFromSocket(\"/dispatch layoutmsg focus l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: d\");\n    }\n\n    OK(getFromSocket(\"/dispatch layoutmsg focus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: a\");\n    }\n\n    // set wrap_focus to false\n    OK(getFromSocket(\"/keyword scrolling:wrap_focus false\"));\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n\n    OK(getFromSocket(\"/dispatch layoutmsg focus l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: a\");\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:d\"));\n\n    OK(getFromSocket(\"/dispatch layoutmsg focus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: d\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic void testSwapcolWrapping() {\n    for (auto const& win : {\"a\", \"b\", \"c\", \"d\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    // set wrap_swapcol to true\n    OK(getFromSocket(\"/keyword scrolling:wrap_swapcol true\"));\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n\n    OK(getFromSocket(\"/dispatch layoutmsg swapcol l\"));\n    OK(getFromSocket(\"/dispatch layoutmsg focus l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: c\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    for (auto const& win : {\"a\", \"b\", \"c\", \"d\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:d\"));\n    OK(getFromSocket(\"/dispatch layoutmsg swapcol r\"));\n    OK(getFromSocket(\"/dispatch layoutmsg focus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    for (auto const& win : {\"a\", \"b\", \"c\", \"d\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    // set wrap_swapcol to false\n    OK(getFromSocket(\"/keyword scrolling:wrap_swapcol false\"));\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n\n    OK(getFromSocket(\"/dispatch layoutmsg swapcol l\"));\n    OK(getFromSocket(\"/dispatch layoutmsg focus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:d\"));\n\n    OK(getFromSocket(\"/dispatch layoutmsg swapcol r\"));\n    OK(getFromSocket(\"/dispatch layoutmsg focus l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: c\");\n    }\n\n    // clean up\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n}\n\nstatic bool testWindowRule() {\n    NLog::log(\"{}Testing Scrolling Width\", Colors::GREEN);\n\n    // inject a new rule.\n    OK(getFromSocket(\"/keyword windowrule[scrolling-width]:match:class kitty_scroll\"));\n    OK(getFromSocket(\"/keyword windowrule[scrolling-width]:scrolling_width 0.1\"));\n\n    if (!Tests::spawnKitty(\"kitty_scroll\")) {\n        NLog::log(\"{}Failed to spawn kitty with win class `kitty_scroll`\", Colors::RED);\n        return false;\n    }\n\n    if (!Tests::spawnKitty(\"kitty_scroll\")) {\n        NLog::log(\"{}Failed to spawn kitty with win class `kitty_scroll`\", Colors::RED);\n        return false;\n    }\n\n    EXPECT(Tests::windowCount(), 2);\n\n    // not the greatest test, but as long as res and gaps don't change, we good.\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"size: 174,1036\");\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n    return true;\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing Scroll layout\", Colors::GREEN);\n\n    // setup\n    OK(getFromSocket(\"/dispatch workspace name:scroll\"));\n    OK(getFromSocket(\"/keyword general:layout scrolling\"));\n\n    // test\n    NLog::log(\"{}Testing focus cycling\", Colors::GREEN);\n    testFocusCycling();\n\n    // test\n    NLog::log(\"{}Testing focus wrap\", Colors::GREEN);\n    testFocusWrapping();\n\n    // test\n    NLog::log(\"{}Testing swapcol wrap\", Colors::GREEN);\n    testSwapcolWrapping();\n\n    testWindowRule();\n\n    // clean up\n    NLog::log(\"Cleaning up\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test);\n"
  },
  {
    "path": "hyprtester/src/tests/main/snap.cpp",
    "content": "#include <hyprutils/math/Vector2D.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <hyprutils/os/Process.hpp>\n\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n\nusing Hyprutils::Math::Vector2D;\n\nstatic int  ret = 0;\n\nstatic bool spawnFloatingKitty() {\n    if (!Tests::spawnKitty()) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n    OK(getFromSocket(\"/dispatch setfloating active\"));\n    OK(getFromSocket(\"/dispatch resizeactive exact 100 100\"));\n    return true;\n}\n\nstatic void expectSocket(const std::string& CMD) {\n    if (const auto RESULT = getFromSocket(CMD); RESULT != \"ok\") {\n        NLog::log(\"{}Failed: {}getFromSocket({}), expected ok, got {}. Source: {}@{}.\", Colors::RED, Colors::RESET, CMD, RESULT, __FILE__, __LINE__);\n        ret = 1;\n        TESTS_FAILED++;\n    } else {\n        NLog::log(\"{}Passed: {}getFromSocket({}). Got ok\", Colors::GREEN, Colors::RESET, CMD);\n        TESTS_PASSED++;\n    }\n}\n\nstatic void expectSnapMove(const Vector2D FROM, const Vector2D* TO) {\n    const Vector2D& A = FROM;\n    const Vector2D& B = TO ? *TO : FROM;\n    if (TO)\n        NLog::log(\"{}Expecting snap to ({},{}) when window is moved to ({},{})\", Colors::YELLOW, B.x, B.y, A.x, A.y);\n    else\n        NLog::log(\"{}Expecting no snap when window is moved to ({},{})\", Colors::YELLOW, A.x, A.y);\n\n    expectSocket(std::format(\"/dispatch moveactive exact {} {}\", A.x, A.y));\n    expectSocket(\"/dispatch plugin:test:snapmove\");\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), std::format(\"at: {},{}\", B.x, B.y));\n}\n\nstatic void testWindowSnap(const bool RESPECTGAPS) {\n    const int BORDERSIZE = 2;\n    const int WINDOWSIZE = 100;\n\n    const int OTHER     = 500;\n    const int WINDOWGAP = 8;\n    const int GAPSIN    = 5;\n    const int GAP       = (RESPECTGAPS ? 2 * GAPSIN : 0) + (2 * BORDERSIZE);\n    const int END       = GAP + WINDOWSIZE;\n\n    int       x;\n    Vector2D  predict;\n\n    x = WINDOWGAP + END;\n    expectSnapMove({OTHER + x, OTHER}, nullptr);\n    expectSnapMove({OTHER - x, OTHER}, nullptr);\n    expectSnapMove({OTHER, OTHER + x}, nullptr);\n    expectSnapMove({OTHER, OTHER - x}, nullptr);\n    x -= 1;\n    expectSnapMove({OTHER + x, OTHER}, &(predict = {OTHER + END, OTHER}));\n    expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER}));\n    expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END}));\n    expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END}));\n}\n\nstatic void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) {\n    const int BORDERSIZE = 2;\n    const int WINDOWSIZE = 100;\n\n    const int MONITORGAP = 10;\n    const int GAPSOUT    = 20;\n    const int RESP       = (RESPECTGAPS ? GAPSOUT : 0);\n    const int GAP        = RESP + (OVERLAP ? 0 : BORDERSIZE);\n    const int END        = GAP + WINDOWSIZE;\n\n    int       x;\n    Vector2D  predict;\n\n    x = MONITORGAP + GAP;\n    expectSnapMove({x, x}, nullptr);\n    x -= 1;\n    expectSnapMove({x, x}, &(predict = {GAP, GAP}));\n\n    x = MONITORGAP + END;\n    expectSnapMove({1920 - x, 1080 - x}, nullptr);\n    x -= 1;\n    expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END}));\n\n    // test reserved area\n    const int RESERVED = 200;\n    const int RGAP     = RESERVED + RESP + BORDERSIZE;\n    const int REND     = RGAP + WINDOWSIZE;\n\n    x = MONITORGAP + RGAP;\n    expectSnapMove({x, x}, nullptr);\n    x -= 1;\n    expectSnapMove({x, x}, &(predict = {RGAP, RGAP}));\n\n    x = MONITORGAP + REND;\n    expectSnapMove({1920 - x, 1080 - x}, nullptr);\n    x -= 1;\n    expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND}));\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing snap\", Colors::GREEN);\n\n    // move to monitor HEADLESS-2\n    NLog::log(\"{}Moving to monitor HEADLESS-2\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-2\"));\n    NLog::log(\"{}Adding reserved monitor area to HEADLESS-2\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword monitor HEADLESS-2,addreserved,200,200,200,200\"));\n\n    // test on workspace \"snap\"\n    NLog::log(\"{}Dispatching workspace `snap`\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace name:snap\"));\n\n    // spawn a kitty terminal and move to (500,500)\n    NLog::log(\"{}Spawning kittyProcA\", Colors::YELLOW);\n    if (!spawnFloatingKitty())\n        return false;\n\n    NLog::log(\"{}Expecting 1 window\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 1);\n\n    NLog::log(\"{}Move the kitty window to (500,500)\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch moveactive exact 500 500\"));\n\n    // spawn a second kitty terminal\n    NLog::log(\"{}Spawning kittyProcB\", Colors::YELLOW);\n    if (!spawnFloatingKitty())\n        return false;\n\n    NLog::log(\"{}Expecting 2 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 2);\n\n    NLog::log(\"\");\n    testWindowSnap(false);\n    testMonitorSnap(false, false);\n\n    NLog::log(\"\\n{}Turning on respect_gaps\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword general:snap:respect_gaps true\"));\n    testWindowSnap(true);\n    testMonitorSnap(true, false);\n\n    NLog::log(\"\\n{}Turning on border_overlap\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword general:snap:respect_gaps false\"));\n    OK(getFromSocket(\"/keyword general:snap:border_overlap true\"));\n    testMonitorSnap(false, true);\n\n    NLog::log(\"\\n{}Turning on both border_overlap and respect_gaps\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword general:snap:respect_gaps true\"));\n    testMonitorSnap(true, true);\n\n    // kill all\n    NLog::log(\"\\n{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    NLog::log(\"{}Reloading the config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/solitary.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nstatic bool test() {\n    NLog::log(\"{}Testing solitary clients\", Colors::GREEN);\n\n    OK(getFromSocket(\"/keyword general:allow_tearing false\"));\n    OK(getFromSocket(\"/keyword render:direct_scanout 0\"));\n    OK(getFromSocket(\"/keyword cursor:no_hardware_cursors 1\"));\n    NLog::log(\"{}Expecting blocked solitary/DS/tearing\", Colors::YELLOW);\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"solitary: 0\\n\");\n        EXPECT_CONTAINS(str, \"solitaryBlockedBy: windowed mode,missing candidate\");\n        EXPECT_CONTAINS(str, \"activelyTearing: false\");\n        EXPECT_CONTAINS(str, \"tearingBlockedBy: next frame is not torn,user settings,not supported by monitor,missing candidate\");\n        EXPECT_CONTAINS(str, \"directScanoutTo: 0\\n\");\n        EXPECT_CONTAINS(str, \"directScanoutBlockedBy: user settings,software renders/cursors,missing candidate\");\n    }\n\n    // FIXME: need a reliable client with solitary opaque surface in fullscreen. kitty doesn't work all the time\n    // NLog::log(\"{}Spawning kittyProcA\", Colors::YELLOW);\n    // auto kittyProcA = Tests::spawnKitty();\n\n    // if (!kittyProcA) {\n    //     NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n    //     return false;\n    // }\n\n    // OK(getFromSocket(\"/keyword general:allow_tearing true\"));\n    // OK(getFromSocket(\"/keyword render:direct_scanout 1\"));\n    // NLog::log(\"{}\", getFromSocket(\"/clients\"));\n    // OK(getFromSocket(\"/dispatch fullscreen\"));\n    // NLog::log(\"{}\", getFromSocket(\"/clients\"));\n    // std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    // NLog::log(\"{}Expecting kitty to almost pass for solitary/DS/tearing\", Colors::YELLOW);\n    // {\n    //     auto str = getFromSocket(\"/monitors\");\n    //     EXPECT_NOT_CONTAINS(str, \"solitary: 0\\n\");\n    //     EXPECT_CONTAINS(str, \"solitaryBlockedBy: null\");\n    //     EXPECT_CONTAINS(str, \"activelyTearing: false\");\n    //     EXPECT_CONTAINS(str, \"tearingBlockedBy: next frame is not torn,not supported by monitor,window settings\");\n    // }\n\n    // OK(getFromSocket(\"/dispatch setprop active immediate 1\"));\n    // NLog::log(\"{}Expecting kitty to almost pass for tearing\", Colors::YELLOW);\n    // {\n    //     auto str = getFromSocket(\"/monitors\");\n    //     EXPECT_CONTAINS(str, \"tearingBlockedBy: next frame is not torn,not supported by monitor\\n\");\n    // }\n\n    // // kill all\n    // NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    // Tests::killAllWindows();\n\n    NLog::log(\"{}Reloading the config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/tags.cpp",
    "content": "#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n\nstatic int  ret = 0;\n\nstatic bool testTags() {\n    NLog::log(\"{}Testing tags\", Colors::GREEN);\n\n    EXPECT(Tests::windowCount(), 0);\n\n    NLog::log(\"{}Spawning kittyProcA&B on ws 1\", Colors::YELLOW);\n    auto kittyProcA = Tests::spawnKitty(\"tagged\");\n    auto kittyProcB = Tests::spawnKitty(\"untagged\");\n\n    if (!kittyProcA || !kittyProcB) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Testing testTag tags\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword windowrule[tag-test-1]:tag +testTag\"));\n    OK(getFromSocket(\"/keyword windowrule[tag-test-1]:match:class tagged\"));\n    OK(getFromSocket(\"/keyword windowrule[tag-test-2]:match:tag negative:testTag\"));\n    OK(getFromSocket(\"/keyword windowrule[tag-test-2]:no_shadow true\"));\n    OK(getFromSocket(\"/keyword windowrule[tag-test-3]:match:tag testTag\"));\n    OK(getFromSocket(\"/keyword windowrule[tag-test-3]:no_dim true\"));\n\n    EXPECT(Tests::windowCount(), 2);\n    OK(getFromSocket(\"/dispatch focuswindow class:tagged\"));\n    NLog::log(\"{}Testing tagged window for no_dim 0 & no_shadow\", Colors::YELLOW);\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"testTag\");\n    EXPECT_CONTAINS(getFromSocket(\"/getprop activewindow no_dim\"), \"true\");\n    EXPECT_CONTAINS(getFromSocket(\"/getprop activewindow no_shadow\"), \"false\");\n    NLog::log(\"{}Testing untagged window for no_dim & no_shadow\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch focuswindow class:untagged\"));\n    EXPECT_NOT_CONTAINS(getFromSocket(\"/activewindow\"), \"testTag\");\n    EXPECT_CONTAINS(getFromSocket(\"/getprop activewindow no_shadow\"), \"true\");\n    EXPECT_CONTAINS(getFromSocket(\"/getprop activewindow no_dim\"), \"false\");\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    OK(getFromSocket(\"/reload\"));\n\n    return ret == 0;\n}\n\nREGISTER_TEST_FN(testTags)\n"
  },
  {
    "path": "hyprtester/src/tests/main/tests.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <functional>\n\ninline std::vector<std::function<bool()>> testFns;\n\n#define REGISTER_TEST_FN(fn)                                                                                                                                                       \\\n    static auto _register_fn = [] {                                                                                                                                                \\\n        testFns.emplace_back(fn);                                                                                                                                                  \\\n        return 1;                                                                                                                                                                  \\\n    }();\n"
  },
  {
    "path": "hyprtester/src/tests/main/window.cpp",
    "content": "#include <unistd.h>\n#include <cmath>\n#include <chrono>\n#include <cstdlib>\n#include <cstring>\n#include <filesystem>\n#include <thread>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <hyprutils/string/VarList2.hpp>\n\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include \"../shared.hpp\"\n#include \"tests.hpp\"\n\nstatic int  ret = 0;\n\nstatic bool spawnKitty(const std::string& class_, const std::vector<std::string>& args = {}) {\n    NLog::log(\"{}Spawning {}\", Colors::YELLOW, class_);\n    if (!Tests::spawnKitty(class_, args)) {\n        NLog::log(\"{}Error: {} did not spawn\", Colors::RED, class_);\n        return false;\n    }\n    return true;\n}\n\n/// Spawns a kitty and creates a file and returns its name. The removal of the file triggers\n/// activation of the spawned kitty window.\n///\n/// On failure, returns an empty string, possibly leaving a temporary file.\nstatic std::string spawnKittyActivating(const std::string& class_ = \"kitty_activating\") {\n    // `XXXXXX` is what `mkstemp` expects to find in the string\n    std::string tmpFilename = (std::filesystem::temp_directory_path() / \"XXXXXX\").string();\n    int         fd          = mkstemp(tmpFilename.data());\n    if (fd < 0) {\n        NLog::log(\"{}Error: could not create tmp file: errno {}\", Colors::RED, errno);\n        return \"\";\n    }\n    (void)close(fd);\n    bool ok =\n        spawnKitty(class_, {\"-o\", \"allow_remote_control=yes\", \"--\", \"/bin/sh\", \"-c\", \"while [ -f \\\"\" + tmpFilename + \"\\\" ]; do :; done; kitten @ focus-window; sleep infinity\"});\n    if (!ok) {\n        NLog::log(\"{}Error: failed to spawn kitty\", Colors::RED);\n        return \"\";\n    }\n    return tmpFilename;\n}\n\nstatic std::string getWindowAddress(const std::string& winInfo) {\n    auto pos  = winInfo.find(\"Window \");\n    auto pos2 = winInfo.find(\" -> \");\n    if (pos == std::string::npos || pos2 == std::string::npos) {\n        NLog::log(\"{}Wrong window info\", Colors::RED);\n        ret = 1;\n        return \"Wrong window info\";\n    }\n    return winInfo.substr(pos + 7, pos2 - pos - 7);\n}\n\nstatic void testSwapWindow() {\n    NLog::log(\"{}Testing swapwindow\", Colors::GREEN);\n\n    // test on workspace \"swapwindow\"\n    NLog::log(\"{}Switching to workspace \\\"swapwindow\\\"\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace name:swapwindow\");\n\n    if (!Tests::spawnKitty(\"kitty_A\")) {\n        ret = 1;\n        return;\n    }\n\n    if (!Tests::spawnKitty(\"kitty_B\")) {\n        ret = 1;\n        return;\n    }\n\n    NLog::log(\"{}Expecting 2 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 2);\n\n    // Test swapwindow by direction\n    {\n        getFromSocket(\"/dispatch focuswindow class:kitty_A\");\n        auto pos = Tests::getWindowAttribute(getFromSocket(\"/activewindow\"), \"at:\");\n        NLog::log(\"{}Testing kitty_A {}, swapwindow with direction 'r'\", Colors::YELLOW, pos);\n\n        OK(getFromSocket(\"/dispatch swapwindow r\"));\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), std::format(\"{}\", pos));\n    }\n\n    // Test swapwindow by class\n    {\n        getFromSocket(\"/dispatch focuswindow class:kitty_A\");\n        auto pos = Tests::getWindowAttribute(getFromSocket(\"/activewindow\"), \"at:\");\n        NLog::log(\"{}Testing kitty_A {}, swapwindow with class:kitty_B\", Colors::YELLOW, pos);\n\n        OK(getFromSocket(\"/dispatch swapwindow class:kitty_B\"));\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), std::format(\"{}\", pos));\n    }\n\n    // Test swapwindow by address\n    {\n        getFromSocket(\"/dispatch focuswindow class:kitty_B\");\n        auto addr = getWindowAddress(getFromSocket(\"/activewindow\"));\n        getFromSocket(\"/dispatch focuswindow class:kitty_A\");\n        auto pos = Tests::getWindowAttribute(getFromSocket(\"/activewindow\"), \"at:\");\n        NLog::log(\"{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)\", Colors::YELLOW, pos, addr);\n\n        OK(getFromSocket(std::format(\"/dispatch swapwindow address:0x{}\", addr)));\n        OK(getFromSocket(std::format(\"/dispatch focuswindow address:0x{}\", addr)));\n\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), std::format(\"{}\", pos));\n    }\n\n    NLog::log(\"{}Testing swapwindow with fullscreen. Expecting to fail\", Colors::YELLOW);\n    {\n        OK(getFromSocket(\"/dispatch fullscreen\"));\n\n        auto str = getFromSocket(\"/dispatch swapwindow l\");\n        EXPECT_CONTAINS(str, \"Can't swap fullscreen window\");\n\n        OK(getFromSocket(\"/dispatch fullscreen\"));\n    }\n\n    NLog::log(\"{}Testing swapwindow with different workspace\", Colors::YELLOW);\n    {\n        getFromSocket(\"/dispatch focuswindow class:kitty_B\");\n        auto addr = getWindowAddress(getFromSocket(\"/activewindow\"));\n        auto ws   = Tests::getWindowAttribute(getFromSocket(\"/activewindow\"), \"workspace:\");\n        NLog::log(\"{}Sending address:0x{}(kitty_B) to workspace \\\"swapwindow2\\\"\", Colors::YELLOW, addr);\n\n        OK(getFromSocket(\"/dispatch movetoworkspacesilent name:swapwindow2\"));\n        OK(getFromSocket(std::format(\"/dispatch swapwindow address:0x{}\", addr)));\n        getFromSocket(\"/dispatch focuswindow class:kitty_B\");\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), std::format(\"{}\", ws));\n    }\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n}\n\nstatic void testGroupRules() {\n    NLog::log(\"{}Testing group window rules\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword general:border_size 8\"));\n    OK(getFromSocket(\"/keyword workspace w[tv1], bordersize:0\"));\n    OK(getFromSocket(\"/keyword workspace f[1], bordersize:0\"));\n    OK(getFromSocket(\"/keyword windowrule match:workspace w[tv1], border_size 0\"));\n    OK(getFromSocket(\"/keyword windowrule match:workspace f[1], border_size 0\"));\n\n    if (!Tests::spawnKitty(\"kitty_A\")) {\n        ret = 1;\n        return;\n    }\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"0\");\n    }\n\n    if (!Tests::spawnKitty(\"kitty_B\")) {\n        ret = 1;\n        return;\n    }\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"8\");\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n    OK(getFromSocket(\"/dispatch togglegroup\"));\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n    OK(getFromSocket(\"/dispatch moveintogroup l\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"0\");\n    }\n\n    OK(getFromSocket(\"/dispatch changegroupactive f\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"0\");\n    }\n\n    if (!Tests::spawnKitty(\"kitty_C\")) {\n        ret = 1;\n        return;\n    }\n\n    OK(getFromSocket(\"/dispatch moveoutofgroup r\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"8\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n}\n\nstatic bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) {\n    std::string activeWin     = getFromSocket(\"/activewindow\");\n    auto        winClass      = Tests::getWindowAttribute(activeWin, \"class:\");\n    auto        winFullscreen = Tests::getWindowAttribute(activeWin, \"fullscreen:\").back();\n    if (winClass.substr(strlen(\"class: \")) == class_ && winFullscreen == fullscreen)\n        return true;\n    else {\n        if (log)\n            NLog::log(\"{}Wrong active window: expected class {} fullscreen '{}', found class {}, fullscreen '{}'\", Colors::RED, class_, fullscreen, winClass, winFullscreen);\n        return false;\n    }\n}\n\nstatic bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) {\n    int cnt = 0;\n    while (!isActiveWindow(class_, fullscreen, false)) {\n        ++cnt;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n        if (cnt > maxTries) {\n            return isActiveWindow(class_, fullscreen, logLastCheck);\n        }\n    }\n    return true;\n}\n\n/// Tests behavior of a window being focused when on that window's workspace\n/// another fullscreen window exists.\nstatic bool testWindowFocusOnFullscreenConflict() {\n    if (!spawnKitty(\"kitty_A\"))\n        return false;\n    if (!spawnKitty(\"kitty_B\"))\n        return false;\n\n    OK(getFromSocket(\"/keyword misc:focus_on_activate true\"));\n\n    // Unfullscreen on conflict\n    {\n        OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 2\"));\n\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n\n        // Dispatch-focus the same window\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n\n        // Dispatch-focus a different window\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n        EXPECT(isActiveWindow(\"kitty_B\", '0'), true);\n\n        // Make a window that will request focus\n        const std::string removeToActivate = spawnKittyActivating();\n        if (removeToActivate.empty())\n            return false;\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n        std::filesystem::remove(removeToActivate);\n        EXPECT(waitForActiveWindow(\"kitty_activating\", '0'), true);\n        OK(getFromSocket(\"/dispatch forcekillactive\"));\n        Tests::waitUntilWindowsN(2);\n    }\n\n    // Take over on conflict\n    {\n        OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 1\"));\n\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n\n        // Dispatch-focus the same window\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n\n        // Dispatch-focus a different window\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n        EXPECT(isActiveWindow(\"kitty_B\", '2'), true);\n        OK(getFromSocket(\"/dispatch fullscreenstate 0 0\"));\n\n        // Make a window that will request focus\n        const std::string removeToActivate = spawnKittyActivating();\n        if (removeToActivate.empty())\n            return false;\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n        std::filesystem::remove(removeToActivate);\n        EXPECT(waitForActiveWindow(\"kitty_activating\", '2'), true);\n        OK(getFromSocket(\"/dispatch forcekillactive\"));\n        Tests::waitUntilWindowsN(2);\n    }\n\n    // Keep the old focus on conflict\n    {\n        OK(getFromSocket(\"/keyword misc:on_focus_under_fullscreen 0\"));\n\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n\n        // Dispatch-focus the same window\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n\n        // Make a window that will request focus - the setting is treated normally\n        const std::string removeToActivate = spawnKittyActivating();\n        if (removeToActivate.empty())\n            return false;\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(isActiveWindow(\"kitty_A\", '2'), true);\n        std::filesystem::remove(removeToActivate);\n        EXPECT(waitForActiveWindow(\"kitty_A\", '2'), true);\n    }\n\n    NLog::log(\"{}Reloading config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return true;\n}\n\nstatic void testMaximizeSize() {\n    NLog::log(\"{}Testing maximize size\", Colors::GREEN);\n\n    EXPECT(spawnKitty(\"kitty_A\"), true);\n\n    // check kitty properties. Maximizing shouldnt change its size\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT(str.contains(\"at: 22,22\"), true);\n        EXPECT(str.contains(\"size: 1876,1036\"), true);\n        EXPECT(str.contains(\"fullscreen: 0\"), true);\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen 1\"));\n\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT(str.contains(\"at: 22,22\"), true);\n        EXPECT(str.contains(\"size: 1876,1036\"), true);\n        EXPECT(str.contains(\"fullscreen: 1\"), true);\n    }\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n}\n\nstatic void testFloatingFocusOnFullscreen() {\n    NLog::log(\"{}Testing floating focus on fullscreen\", Colors::GREEN);\n\n    EXPECT(spawnKitty(\"kitty_A\"), true);\n    OK(getFromSocket(\"/dispatch togglefloating\"));\n\n    EXPECT(spawnKitty(\"kitty_B\"), true);\n    OK(getFromSocket(\"/dispatch fullscreen 1\"));\n\n    OK(getFromSocket(\"/dispatch cyclenext\"));\n\n    OK(getFromSocket(\"/dispatch plugin:test:floating_focus_on_fullscreen\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n}\n\nstatic void testGroupFallbackFocus() {\n    NLog::log(\"{}Testing group fallback focus\", Colors::GREEN);\n\n    EXPECT(spawnKitty(\"kitty_A\"), true);\n\n    OK(getFromSocket(\"/dispatch togglegroup\"));\n\n    EXPECT(spawnKitty(\"kitty_B\"), true);\n    EXPECT(spawnKitty(\"kitty_C\"), true);\n    EXPECT(spawnKitty(\"kitty_D\"), true);\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"class: kitty_D\"), true);\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_D\"));\n    OK(getFromSocket(\"/dispatch killactive\"));\n\n    Tests::waitUntilWindowsN(3);\n\n    // Focus must return to the last focus, in this case B.\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"class: kitty_B\"), true);\n    }\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n}\n\nstatic void testBringActiveToTopMouseMovement() {\n    NLog::log(\"{}Testing bringactivetotop mouse movement\", Colors::GREEN);\n\n    Tests::killAllWindows();\n    OK(getFromSocket(\"/keyword input:follow_mouse 2\"));\n    OK(getFromSocket(\"/keyword input:float_switch_override_focus 0\"));\n\n    EXPECT(spawnKitty(\"a\"), true);\n    OK(getFromSocket(\"/dispatch setfloating\"));\n    OK(getFromSocket(\"/dispatch movewindowpixel exact 500 300,activewindow\"));\n    OK(getFromSocket(\"/dispatch resizewindowpixel exact 400 400,activewindow\"));\n\n    EXPECT(spawnKitty(\"b\"), true);\n    OK(getFromSocket(\"/dispatch setfloating\"));\n    OK(getFromSocket(\"/dispatch movewindowpixel exact 500 300,activewindow\"));\n    OK(getFromSocket(\"/dispatch resizewindowpixel exact 400 400,activewindow\"));\n\n    auto getTopWindow = []() -> std::string {\n        auto clients = getFromSocket(\"/clients\");\n        return (clients.rfind(\"class: a\") > clients.rfind(\"class: b\")) ? \"a\" : \"b\";\n    };\n\n    EXPECT(getTopWindow(), std::string(\"b\"));\n    OK(getFromSocket(\"/dispatch movecursor 700 500\"));\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n    EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"class: a\");\n\n    OK(getFromSocket(\"/dispatch bringactivetotop\"));\n    EXPECT(getTopWindow(), std::string(\"a\"));\n\n    OK(getFromSocket(\"/dispatch plugin:test:click 272,1\"));\n    OK(getFromSocket(\"/dispatch plugin:test:click 272,0\"));\n\n    EXPECT(getTopWindow(), std::string(\"a\"));\n\n    Tests::killAllWindows();\n}\n\nstatic void testInitialFloatSize() {\n    NLog::log(\"{}Testing initial float size\", Colors::GREEN);\n\n    Tests::killAllWindows();\n    OK(getFromSocket(\"/keyword windowrule match:class kitty, float yes\"));\n    OK(getFromSocket(\"/keyword input:float_switch_override_focus 0\"));\n\n    EXPECT(spawnKitty(\"kitty\"), true);\n\n    {\n        // Kitty by default opens as 640x400, if this changes this test will break\n        auto str = getFromSocket(\"/clients\");\n        EXPECT(str.contains(\"size: 640,400\"), true);\n    }\n\n    OK(getFromSocket(\"/reload\"));\n\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/dispatch exec [float yes]kitty\"));\n\n    Tests::waitUntilWindowsN(1);\n\n    {\n        // Kitty by default opens as 640x400, if this changes this test will break\n        auto str = getFromSocket(\"/clients\");\n        EXPECT(str.contains(\"size: 640,400\"), true);\n        EXPECT(str.contains(\"floating: 1\"), true);\n    }\n\n    Tests::killAllWindows();\n}\n\n/// Tests that the `focus_on_activate` effect of window rules always overrides\n/// the `misc:focus_on_activate` variable.\nstatic bool testWindowRuleFocusOnActivate() {\n    OK(getFromSocket(\"/reload\"));\n\n    if (!spawnKitty(\"kitty_default\")) {\n        NLog::log(\"{}Error: failed to spawn kitty\", Colors::RED);\n        return false;\n    }\n\n    // Do not focus anyone automatically\n    ///////////OK(getFromSocket(\"/keyword windowrule match:class .*, no_initial_focus true\"));\n\n    // `focus_on_activate off` takes over\n    {\n        OK(getFromSocket(\"/keyword misc:focus_on_activate true\"));\n        OK(getFromSocket(\"/keyword windowrule match:class kitty_antifocus, focus_on_activate off\"));\n\n        const std::string removeToActivate = spawnKittyActivating(\"kitty_antifocus\");\n        if (removeToActivate.empty()) {\n            return false;\n        }\n        EXPECT(waitForActiveWindow(\"kitty_antifocus\"), true);\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_default\"));\n        EXPECT(isActiveWindow(\"kitty_default\"), true);\n\n        std::filesystem::remove(removeToActivate);\n        // The focus should NOT transition, since the window rule explicitly forbids that\n        EXPECT(waitForActiveWindow(\"kitty_antifocus\", '0', false), false);\n    }\n\n    // `focus_on_activate on` takes over\n    {\n        OK(getFromSocket(\"/keyword misc:focus_on_activate false\"));\n        OK(getFromSocket(\"/keyword windowrule match:class kitty_superfocus, focus_on_activate on\"));\n\n        const std::string removeToActivate = spawnKittyActivating(\"kitty_superfocus\");\n        if (removeToActivate.empty()) {\n            return false;\n        }\n        EXPECT(waitForActiveWindow(\"kitty_superfocus\"), true);\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_default\"));\n        EXPECT(isActiveWindow(\"kitty_default\"), true);\n\n        std::filesystem::remove(removeToActivate);\n        // Now that we requested activation, the focus SHOULD transition to kitty_superfocus, according to the window rule\n        EXPECT(waitForActiveWindow(\"kitty_superfocus\"), true);\n    }\n\n    NLog::log(\"{}Reloading config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return true;\n}\n\n// tests if a pinned window contains the valid workspace after change\nstatic bool testPinnedWorkspacesValid() {\n    OK(getFromSocket(\"/reload\"));\n    getFromSocket(\"/dispatch workspace 1337\");\n\n    if (!spawnKitty(\"kitty\")) {\n        NLog::log(\"{}Error: failed to spawn kitty\", Colors::RED);\n        return false;\n    }\n\n    OK(getFromSocket(\"/dispatch setfloating class:kitty\"));\n    OK(getFromSocket(\"/dispatch pin class:kitty\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"workspace: 1337\"), true);\n        EXPECT(str.contains(\"pinned: 1\"), true);\n    }\n\n    getFromSocket(\"/dispatch workspace 1338\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"workspace: 1338\"), true);\n        EXPECT(str.contains(\"pinned: 1\"), true);\n    }\n\n    OK(getFromSocket(\"/dispatch settiled class:kitty\"))\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"workspace: 1338\"), true);\n        EXPECT(str.contains(\"pinned: 0\"), true);\n    }\n\n    NLog::log(\"{}Reloading config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return true;\n}\n\nstatic bool testWindowRuleWorkspaceEmpty() {\n    NLog::log(\"{}Testing windowrule workspace empty\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    OK(getFromSocket(\"/keyword windowrule match:class kitty_A, workspace empty\"));\n    OK(getFromSocket(\"/keyword windowrule match:class kitty_B, workspace emptyn\"));\n\n    getFromSocket(\"/dispatch workspace 3\");\n\n    if (!spawnKitty(\"kitty\")) {\n        NLog::log(\"{}Error: failed to spawn kitty\", Colors::RED);\n        return false;\n    }\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"workspace: 3\"), true);\n    }\n\n    if (!spawnKitty(\"kitty_A\")) {\n        NLog::log(\"{}Error: failed to spawn kitty\", Colors::RED);\n        return false;\n    }\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"workspace: 1\"), true);\n    }\n\n    getFromSocket(\"/dispatch workspace 3\");\n    if (!spawnKitty(\"kitty_B\")) {\n        NLog::log(\"{}Error: failed to spawn kitty\", Colors::RED);\n        return false;\n    }\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT(str.contains(\"workspace: 4\"), true);\n    }\n\n    Tests::killAllWindows();\n\n    return true;\n}\n\nstatic bool testContentRules() {\n    NLog::log(\"{}Testing content window rules\", Colors::YELLOW);\n\n    // kill me PLEASE\n\n    OK(getFromSocket(\"/keyword windowrule match:class kitty_content_string, content game\"));\n    OK(getFromSocket(\"/keyword windowrule match:class kitty_content_numbers, content 3\"));\n    OK(getFromSocket(\"/keyword windowrule match:content game, border_size 10\"));\n    OK(getFromSocket(\"/keyword windowrule match:content 3, opacity 0.5\"));\n\n    const auto testProps = []() {\n        EXPECT_CONTAINS(getFromSocket(\"/getprop active border_size\"), \"10\");\n        EXPECT_CONTAINS(getFromSocket(\"/getprop active opacity\"), \"0.5\");\n    };\n    if (!spawnKitty(\"kitty_content_string\"))\n        return false;\n    waitForActiveWindow(\"kitty_content_string\");\n    testProps();\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n\n    if (!spawnKitty(\"kitty_content_numbers\"))\n        return false;\n    waitForActiveWindow(\"kitty_content_numbers\");\n    testProps();\n\n    Tests::killAllWindows();\n    EXPECT(Tests::windowCount(), 0);\n    return true;\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing windows\", Colors::GREEN);\n\n    // test on workspace \"window\"\n    NLog::log(\"{}Switching to workspace `window`\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace name:window\");\n\n    if (!spawnKitty(\"kitty_A\"))\n        return false;\n\n    // check kitty properties. One kitty should take the entire screen, as this is smart gaps\n    NLog::log(\"{}Expecting kitty_A to take up the whole screen\", Colors::YELLOW);\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT(str.contains(\"at: 0,0\"), true);\n        EXPECT(str.contains(\"size: 1920,1080\"), true);\n        EXPECT(str.contains(\"fullscreen: 0\"), true);\n    }\n\n    NLog::log(\"{}Testing window split ratios\", Colors::YELLOW);\n    {\n        const double INITIAL_RATIO = 1.25;\n        const int    GAPSIN        = 5;\n        const int    GAPSOUT       = 20;\n        const int    BORDERSIZE    = 2;\n        const int    BORDERS       = BORDERSIZE * 2;\n        const int    MONITOR_W     = 1920;\n        const int    MONITOR_H     = 1080;\n\n        const float  totalAvailableHeight   = MONITOR_H - (GAPSOUT * 2);\n        const int    HEIGHT                 = std::floor(totalAvailableHeight) - BORDERS;\n        const float  availableWidthForSplit = MONITOR_W - (GAPSOUT * 2) - GAPSIN;\n\n        auto         calculateFinalWidth = [&](double boxWidth, bool isLeftWindow) {\n            double gapLeft  = isLeftWindow ? GAPSOUT : GAPSIN;\n            double gapRight = isLeftWindow ? GAPSIN : GAPSOUT;\n            return std::floor(boxWidth - gapLeft - gapRight - BORDERS);\n        };\n\n        double       geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);\n        double       geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1;\n        const int    WIDTH1           = calculateFinalWidth(geomBoxWidthB_R1, false);\n\n        const double INVERTED_RATIO   = 0.75;\n        double       geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);\n        double       geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2;\n        const int    WIDTH2           = calculateFinalWidth(geomBoxWidthB_R2, false);\n        const int    WIDTH_A_FINAL    = calculateFinalWidth(geomBoxWidthA_R2, true);\n\n        OK(getFromSocket(\"/keyword dwindle:default_split_ratio 1.25\"));\n\n        if (!spawnKitty(\"kitty_B\"))\n            return false;\n\n        NLog::log(\"{}Expecting kitty_B size: {},{}\", Colors::YELLOW, WIDTH1, HEIGHT);\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), std::format(\"size: {},{}\", WIDTH1, HEIGHT));\n\n        OK(getFromSocket(\"/dispatch killwindow activewindow\"));\n        Tests::waitUntilWindowsN(1);\n\n        NLog::log(\"{}Inverting the split ratio\", Colors::YELLOW);\n        OK(getFromSocket(\"/keyword dwindle:default_split_ratio 0.75\"));\n\n        if (!spawnKitty(\"kitty_B\"))\n            return false;\n\n        try {\n            NLog::log(\"{}Expecting kitty_B size: {},{}\", Colors::YELLOW, WIDTH2, HEIGHT);\n\n            {\n                auto data = getFromSocket(\"/activewindow\");\n                data      = data.substr(data.find(\"size:\") + 5);\n                data      = data.substr(0, data.find('\\n'));\n\n                Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');\n\n                EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH2, 2);\n                EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);\n            }\n\n            OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n            NLog::log(\"{}Expecting kitty_A size: {},{}\", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT);\n\n            {\n                auto data = getFromSocket(\"/activewindow\");\n                data      = data.substr(data.find(\"size:\") + 5);\n                data      = data.substr(0, data.find('\\n'));\n\n                Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');\n\n                EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH_A_FINAL, 2);\n                EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);\n            }\n\n        } catch (...) {\n            NLog::log(\"{}Exception thrown\", Colors::RED);\n            EXPECT(false, true);\n        }\n\n        OK(getFromSocket(\"/keyword dwindle:default_split_ratio 1\"));\n    }\n\n    // open xeyes\n    NLog::log(\"{}Spawning xeyes\", Colors::YELLOW);\n    getFromSocket(\"/dispatch exec xeyes\");\n\n    NLog::log(\"{}Keep checking if xeyes spawned\", Colors::YELLOW);\n    Tests::waitUntilWindowsN(3);\n\n    NLog::log(\"{}Expecting 3 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 3);\n\n    NLog::log(\"{}Checking props of xeyes\", Colors::YELLOW);\n    // check some window props of xeyes, try to float it\n    {\n        auto str = getFromSocket(\"/clients\");\n        EXPECT_NOT_CONTAINS(str, \"floating: 1\");\n        getFromSocket(\"/dispatch setfloating class:XEyes\");\n        std::this_thread::sleep_for(std::chrono::milliseconds(200));\n        str = getFromSocket(\"/clients\");\n        EXPECT_CONTAINS(str, \"floating: 1\");\n    }\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    testSwapWindow();\n\n    getFromSocket(\"/dispatch workspace 1\");\n\n    if (!testWindowFocusOnFullscreenConflict()) {\n        ret = 1;\n        return false;\n    }\n\n    NLog::log(\"{}Testing spawning a floating window over a fullscreen window\", Colors::YELLOW);\n    {\n        if (!spawnKitty(\"kitty_A\"))\n            return false;\n        OK(getFromSocket(\"/dispatch fullscreen 0 set\"));\n        EXPECT(Tests::windowCount(), 1);\n\n        OK(getFromSocket(\"/dispatch exec [float] kitty\"));\n        Tests::waitUntilWindowsN(2);\n\n        OK(getFromSocket(\"/dispatch focuswindow class:^kitty$\"));\n        const auto focused1 = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(focused1, \"class: kitty\\n\");\n\n        OK(getFromSocket(\"/dispatch killwindow activewindow\"));\n        Tests::waitUntilWindowsN(1);\n\n        // The old window should be focused again\n        const auto focused2 = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(focused2, \"class: kitty_A\\n\");\n\n        NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n        Tests::killAllWindows();\n    }\n\n    NLog::log(\"{}Testing minsize/maxsize rules for tiled windows\", Colors::YELLOW);\n    {\n        // Enable the config for testing, test max/minsize for tiled windows and centering\n        OK(getFromSocket(\"/keyword misc:size_limits_tiled 1\"));\n        OK(getFromSocket(\"/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize\"));\n        OK(getFromSocket(\"/keyword windowrule[kitty-max-rule]:max_size 1500 500\"));\n        OK(getFromSocket(\"r/keyword windowrule[kitty-max-rule]:min_size 1200 500\"));\n        if (!spawnKitty(\"kitty_maxsize\"))\n            return false;\n\n        auto dwindle = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(dwindle, \"size: 1500,500\");\n        EXPECT_CONTAINS(dwindle, \"at: 210,290\");\n\n        // Fuck this test, it's fucking stupid - vax\n        // if (!spawnKitty(\"kitty_maxsize\"))\n        //     return false;\n        // EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"size: 1200,500\");\n\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n\n        OK(getFromSocket(\"/keyword general:layout master\"));\n\n        if (!spawnKitty(\"kitty_maxsize\"))\n            return false;\n\n        auto master = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(master, \"size: 1500,500\");\n        EXPECT_CONTAINS(master, \"at: 210,290\");\n\n        if (!spawnKitty(\"kitty_maxsize\"))\n            return false;\n\n        // FIXME: I can't be arsed.\n        OK(getFromSocket(\"/dispatch focuswindow class:kitty_maxsize\"));\n        //        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"size: 1200,500\")\n\n        NLog::log(\"{}Reloading config\", Colors::YELLOW);\n        OK(getFromSocket(\"/reload\"));\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n    }\n\n    NLog::log(\"{}Testing minsize/maxsize rules\", Colors::YELLOW);\n    {\n        // Disable size limits tiled and check if props are working and not getting skipped\n        OK(getFromSocket(\"/keyword misc:size_limits_tiled 0\"));\n        OK(getFromSocket(\"/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize\"));\n        OK(getFromSocket(\"/keyword windowrule[kitty-max-rule]:max_size 1500 500\"));\n        OK(getFromSocket(\"r/keyword windowrule[kitty-max-rule]:min_size 1200 500\"));\n        if (!spawnKitty(\"kitty_maxsize\"))\n            return false;\n\n        {\n            auto res = getFromSocket(\"/getprop active max_size\");\n            EXPECT_CONTAINS(res, \"1500\");\n            EXPECT_CONTAINS(res, \"500\");\n        }\n\n        {\n            auto res = getFromSocket(\"/getprop active min_size\");\n            EXPECT_CONTAINS(res, \"1200\");\n            EXPECT_CONTAINS(res, \"500\");\n        }\n\n        NLog::log(\"{}Reloading config\", Colors::YELLOW);\n        OK(getFromSocket(\"/reload\"));\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n    }\n\n    {\n        // Set float\n        OK(getFromSocket(\"/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize\"));\n        OK(getFromSocket(\"/keyword windowrule[kitty-max-rule]:max_size 1200 500\"));\n        OK(getFromSocket(\"r/keyword windowrule[kitty-max-rule]:min_size 1200 500\"));\n        OK(getFromSocket(\"r/keyword windowrule[kitty-max-rule]:float yes\"));\n        if (!spawnKitty(\"kitty_maxsize\"))\n            return false;\n\n        {\n            auto res = getFromSocket(\"/getprop active max_size\");\n            EXPECT_CONTAINS(res, \"1200\");\n            EXPECT_CONTAINS(res, \"500\");\n        }\n\n        {\n            auto res = getFromSocket(\"/getprop active min_size\");\n            EXPECT_CONTAINS(res, \"1200\");\n            EXPECT_CONTAINS(res, \"500\");\n        }\n\n        {\n            auto res = getFromSocket(\"/activewindow\");\n            EXPECT_CONTAINS(res, \"size: 1200,500\");\n        }\n\n        NLog::log(\"{}Reloading config\", Colors::YELLOW);\n        OK(getFromSocket(\"/reload\"));\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n    }\n\n    NLog::log(\"{}Testing window rules\", Colors::YELLOW);\n    if (!spawnKitty(\"wr_kitty\"))\n        return false;\n    {\n        auto      str  = getFromSocket(\"/activewindow\");\n        const int SIZE = 200;\n        EXPECT_CONTAINS(str, \"floating: 1\");\n        EXPECT_CONTAINS(str, std::format(\"size: {},{}\", SIZE, SIZE));\n        EXPECT_NOT_CONTAINS(str, \"pinned: 1\");\n    }\n\n    OK(getFromSocket(\"/keyword windowrule[wr-kitty-stuff]:opacity 0.5 0.5 override\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active opacity\");\n        EXPECT_CONTAINS(str, \"0.5\");\n    }\n\n    OK(getFromSocket(\"/keyword windowrule[special-magic-kitty]:match:class magic_kitty\"));\n    OK(getFromSocket(\"/keyword windowrule[special-magic-kitty]:workspace special:magic\"));\n\n    if (!spawnKitty(\"magic_kitty\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"special:magic\");\n        EXPECT_NOT_CONTAINS(str, \"workspace: 9\");\n    }\n\n    if (auto str = getFromSocket(\"/monitors\"); str.contains(\"magic)\")) {\n        OK(getFromSocket(\"/dispatch togglespecialworkspace magic\"));\n    }\n\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/keyword windowrule[border-magic-kitty]:match:class border_kitty\"));\n    OK(getFromSocket(\"/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg\"));\n\n    if (!spawnKitty(\"border_kitty\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch focuswindow class:border_kitty\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active active_border_color\");\n        EXPECT_CONTAINS(str, \"ffc6ff00\");\n        EXPECT_CONTAINS(str, \"eeff0000\");\n        EXPECT_CONTAINS(str, \"45deg\");\n    }\n\n    Tests::killAllWindows();\n\n    if (!spawnKitty(\"tag_kitty\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"floating: 1\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    // test rules that overlap effects but don't overlap props\n    OK(getFromSocket(\"/keyword windowrule match:class overlap_kitty, border_size 0\"));\n    OK(getFromSocket(\"/keyword windowrule match:fullscreen false, border_size 10\"));\n\n    if (!spawnKitty(\"overlap_kitty\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"10\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    // test persistent_size between floating window launches\n    OK(getFromSocket(\"/keyword windowrule match:class persistent_size_kitty, persistent_size true, float true\"));\n\n    if (!spawnKitty(\"persistent_size_kitty\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch resizeactive exact 600 400\"))\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"size: 600,400\");\n        EXPECT_CONTAINS(str, \"floating: 1\");\n    }\n\n    Tests::killAllWindows();\n\n    if (!spawnKitty(\"persistent_size_kitty\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"size: 600,400\");\n        EXPECT_CONTAINS(str, \"floating: 1\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/keyword general:border_size 0\"));\n    OK(getFromSocket(\"/keyword windowrule match:float true, border_size 10\"));\n\n    if (!spawnKitty(\"border_kitty\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"0\");\n    }\n\n    OK(getFromSocket(\"/dispatch togglefloating\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"10\");\n    }\n\n    OK(getFromSocket(\"/dispatch togglefloating\"));\n\n    {\n        auto str = getFromSocket(\"/getprop active border_size\");\n        EXPECT_CONTAINS(str, \"0\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    // test expression rules\n    OK(getFromSocket(\"/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, \"\n                     \"max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5\"));\n\n    if (!spawnKitty(\"expr_kitty\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"floating: 1\");\n        EXPECT_CONTAINS(str, \"at: 212,540\");\n        EXPECT_CONTAINS(str, \"size: 960,540\");\n\n        auto min = getFromSocket(\"/getprop active min_size\");\n        EXPECT_CONTAINS(min, \"480\");\n        EXPECT_CONTAINS(min, \"270\");\n\n        auto max = getFromSocket(\"/getprop active max_size\");\n        EXPECT_CONTAINS(max, \"1440\");\n        EXPECT_CONTAINS(max, \"810\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/dispatch plugin:test:add_window_rule\"));\n    OK(getFromSocket(\"/reload\"));\n\n    OK(getFromSocket(\"/keyword windowrule match:class plugin_kitty, plugin_rule effect\"));\n\n    if (!spawnKitty(\"plugin_kitty\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch plugin:test:check_window_rule\"));\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    OK(getFromSocket(\"/dispatch plugin:test:add_window_rule\"));\n    OK(getFromSocket(\"/reload\"));\n\n    OK(getFromSocket(\"/keyword windowrule[test-plugin-rule]:match:class plugin_kitty\"));\n    OK(getFromSocket(\"/keyword windowrule[test-plugin-rule]:plugin_rule effect\"));\n\n    if (!spawnKitty(\"plugin_kitty\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch plugin:test:check_window_rule\"));\n\n    OK(getFromSocket(\"/reload\"));\n    Tests::killAllWindows();\n\n    testGroupRules();\n    testMaximizeSize();\n    testFloatingFocusOnFullscreen();\n    testBringActiveToTopMouseMovement();\n    testGroupFallbackFocus();\n    testInitialFloatSize();\n    testWindowRuleFocusOnActivate();\n    testPinnedWorkspacesValid();\n    testWindowRuleWorkspaceEmpty();\n    testContentRules();\n\n    NLog::log(\"{}Reloading config\", Colors::YELLOW);\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/main/workspaces.cpp",
    "content": "#include \"tests.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <print>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nstatic int ret = 0;\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\nusing namespace Hyprutils::Utils;\n\n#define UP CUniquePointer\n#define SP CSharedPointer\n\nstatic bool testSpecialWorkspaceFullscreen() {\n    NLog::log(\"{}Testing special workspace fullscreen detection\", Colors::YELLOW);\n\n    CScopeGuard guard = {[&]() {\n        NLog::log(\"{}Cleaning up special workspace fullscreen test\", Colors::YELLOW);\n        // Close special workspace if open\n        auto monitors = getFromSocket(\"/monitors\");\n        if (monitors.contains(\"(special:test_fs_special)\") && !monitors.contains(\"special workspace: 0 ()\"))\n            getFromSocket(\"/dispatch togglespecialworkspace test_fs_special\");\n        Tests::killAllWindows();\n        OK(getFromSocket(\"/reload\"));\n    }};\n\n    getFromSocket(\"/dispatch workspace 1\");\n    EXPECT(Tests::windowCount(), 0);\n\n    NLog::log(\"{}Test 1: Fullscreen detection on special workspace\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/dispatch workspace special:test_fs_special\"));\n\n    if (!Tests::spawnKitty(\"kitty_special\"))\n        return false;\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_special\");\n        EXPECT_CONTAINS(str, \"(special:test_fs_special)\");\n    }\n\n    OK(getFromSocket(\"/dispatch fullscreen 0\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n    }\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"(special:test_fs_special)\");\n    }\n\n    NLog::log(\"{}Test 2: Special workspace fullscreen precedence\", Colors::YELLOW);\n\n    // Close special workspace before spawning on regular workspace\n    OK(getFromSocket(\"/dispatch togglespecialworkspace test_fs_special\"));\n    getFromSocket(\"/dispatch workspace 1\");\n\n    if (!Tests::spawnKitty(\"kitty_regular\"))\n        return false;\n\n    OK(getFromSocket(\"/dispatch fullscreen 0\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_regular\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n    }\n\n    OK(getFromSocket(\"/dispatch togglespecialworkspace test_fs_special\"));\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_special\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_special\");\n    }\n\n    NLog::log(\"{}Test 3: Toggle special workspace hides it\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/dispatch togglespecialworkspace test_fs_special\"));\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_regular\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_regular\");\n        EXPECT_CONTAINS(str, \"fullscreen: 2\");\n    }\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"special workspace: 0 ()\");\n    }\n\n    return true;\n}\n\nstatic bool testAsymmetricGaps() {\n    NLog::log(\"{}Testing asymmetric gap splits\", Colors::YELLOW);\n    {\n\n        CScopeGuard guard = {[&]() {\n            NLog::log(\"{}Cleaning up asymmetric gap test\", Colors::YELLOW);\n            Tests::killAllWindows();\n            OK(getFromSocket(\"/reload\"));\n        }};\n\n        OK(getFromSocket(\"/dispatch workspace name:gap_split_test\"));\n        OK(getFromSocket(\"r/keyword general:gaps_in 0\"));\n        OK(getFromSocket(\"r/keyword general:border_size 0\"));\n        OK(getFromSocket(\"r/keyword dwindle:split_width_multiplier 1.0\"));\n        OK(getFromSocket(\"r/keyword workspace name:gap_split_test,gapsout:0 1000 0 0\"));\n\n        NLog::log(\"{}Testing default split (force_split = 0)\", Colors::YELLOW);\n        OK(getFromSocket(\"r/keyword dwindle:force_split 0\"));\n\n        if (!Tests::spawnKitty(\"gaps_kitty_A\") || !Tests::spawnKitty(\"gaps_kitty_B\"))\n            return false;\n\n        NLog::log(\"{}Expecting vertical split (B below A)\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_A\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,0\");\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_B\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,540\");\n\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n\n        NLog::log(\"{}Testing force_split = 1\", Colors::YELLOW);\n        OK(getFromSocket(\"r/keyword dwindle:force_split 1\"));\n\n        if (!Tests::spawnKitty(\"gaps_kitty_A\") || !Tests::spawnKitty(\"gaps_kitty_B\"))\n            return false;\n\n        NLog::log(\"{}Expecting vertical split (B above A)\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_B\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,0\");\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_A\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,540\");\n\n        NLog::log(\"{}Expecting horizontal split (C left of B)\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_B\"));\n\n        if (!Tests::spawnKitty(\"gaps_kitty_C\"))\n            return false;\n\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_C\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,0\");\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_B\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 460,0\");\n\n        Tests::killAllWindows();\n        EXPECT(Tests::windowCount(), 0);\n\n        NLog::log(\"{}Testing force_split = 2\", Colors::YELLOW);\n        OK(getFromSocket(\"r/keyword dwindle:force_split 2\"));\n\n        if (!Tests::spawnKitty(\"gaps_kitty_A\") || !Tests::spawnKitty(\"gaps_kitty_B\"))\n            return false;\n\n        NLog::log(\"{}Expecting vertical split (B below A)\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_A\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,0\");\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_B\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,540\");\n\n        NLog::log(\"{}Expecting horizontal split (C right of A)\", Colors::YELLOW);\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_A\"));\n\n        if (!Tests::spawnKitty(\"gaps_kitty_C\"))\n            return false;\n\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_A\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 0,0\");\n        OK(getFromSocket(\"/dispatch focuswindow class:gaps_kitty_C\"));\n        EXPECT_CONTAINS(getFromSocket(\"/activewindow\"), \"at: 460,0\");\n    }\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    return true;\n}\n\nstatic void testMultimonBAF() {\n    NLog::log(\"{}Testing multimon back and forth\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword binds:workspace_back_and_forth 1\"));\n\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-2\"));\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    Tests::spawnKitty();\n\n    OK(getFromSocket(\"/dispatch workspace 2\"));\n\n    Tests::spawnKitty();\n\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-3\"));\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n\n    Tests::spawnKitty();\n\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 2 \");\n    }\n\n    OK(getFromSocket(\"/dispatch workspace 4\"));\n    OK(getFromSocket(\"/dispatch workspace 4\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 2 \");\n    }\n\n    OK(getFromSocket(\"/dispatch workspace 2\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 4 \");\n    }\n\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 4 \");\n    }\n\n    OK(getFromSocket(\"/dispatch workspace 2\"));\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 3 \");\n    }\n\n    Tests::killAllWindows();\n}\n\nstatic void testMultimonFocus() {\n    NLog::log(\"{}Testing multimon focus and move\", Colors::YELLOW);\n\n    OK(getFromSocket(\"/keyword input:follow_mouse 0\"));\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-3\"));\n    OK(getFromSocket(\"/dispatch workspace 8\"));\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-2\"));\n    OK(getFromSocket(\"/dispatch workspace 7\"));\n\n    for (auto const& win : {\"a\", \"b\"}) {\n        if (!Tests::spawnKitty(win)) {\n            NLog::log(\"{}Failed to spawn kitty with win class `{}`\", Colors::RED, win);\n            ++TESTS_FAILED;\n            ret = 1;\n            return;\n        }\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:a\"));\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 7 \");\n    }\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 8 \");\n    }\n\n    Tests::spawnKitty(\"c\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: c\");\n    }\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 8 \");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 7 \");\n    }\n\n    OK(getFromSocket(\"/dispatch movewindow r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 8 \");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: c\");\n    }\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 8 \");\n    }\n\n    OK(getFromSocket(\"/dispatch movefocus l\"));\n\n    OK(getFromSocket(\"/keyword general:no_focus_fallback true\"));\n    OK(getFromSocket(\"/keyword binds:window_direction_monitor_fallback false\"));\n\n    EXPECT_NOT(getFromSocket(\"/dispatch movefocus l\"), \"ok\");\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 8 \");\n    }\n\n    OK(getFromSocket(\"/dispatch movewindow l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: b\");\n    }\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_CONTAINS(str, \"workspace ID 8 \");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n\n    Tests::killAllWindows();\n}\n\nstatic void testDynamicWsEffects() {\n    // test dynamic workspace effects, they shouldn't lag\n\n    OK(getFromSocket(\"/dispatch workspace 69\"));\n\n    Tests::spawnKitty(\"bitch\");\n\n    OK(getFromSocket(\"r/keyword workspace 69,bordersize:20\"));\n    OK(getFromSocket(\"r/keyword workspace 69,rounding:false\"));\n\n    EXPECT(getFromSocket(\"/getprop class:bitch border_size\"), \"20\");\n    EXPECT(getFromSocket(\"/getprop class:bitch rounding\"), \"0\");\n\n    OK(getFromSocket(\"/reload\"));\n\n    Tests::killAllWindows();\n}\n\nstatic bool test() {\n    NLog::log(\"{}Testing workspaces\", Colors::GREEN);\n\n    EXPECT(Tests::windowCount(), 0);\n\n    // test on workspace \"window\"\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    getFromSocket(\"/dispatch workspace 1\");\n\n    NLog::log(\"{}Checking persistent no-mon\", Colors::YELLOW);\n    OK(getFromSocket(\"r/keyword workspace 966,persistent:1\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_CONTAINS(str, \"workspace ID 966 (966)\");\n    }\n\n    OK(getFromSocket(\"/reload\"));\n\n    NLog::log(\"{}Spawning kittyProc on ws 1\", Colors::YELLOW);\n    auto kittyProcA = Tests::spawnKitty();\n\n    if (!kittyProcA) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Switching to workspace 3\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n\n    NLog::log(\"{}Spawning kittyProc on ws 3\", Colors::YELLOW);\n    auto kittyProcB = Tests::spawnKitty();\n\n    if (!kittyProcB) {\n        NLog::log(\"{}Error: kitty did not spawn\", Colors::RED);\n        return false;\n    }\n\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    NLog::log(\"{}Switching to workspace +1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace +1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 2 (2)\");\n    }\n\n    // check if the other workspaces are alive\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_CONTAINS(str, \"workspace ID 3 (3)\");\n        EXPECT_CONTAINS(str, \"workspace ID 1 (1)\");\n    }\n\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    {\n        auto str = getFromSocket(\"/workspaces\");\n        EXPECT_NOT_CONTAINS(str, \"workspace ID 2 (2)\");\n    }\n\n    NLog::log(\"{}Switching to workspace m+1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace m+1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    NLog::log(\"{}Switching to workspace -1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace -1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 2 (2)\");\n    }\n\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    NLog::log(\"{}Switching to workspace r+1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace r+1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 2 (2)\");\n    }\n\n    NLog::log(\"{}Switching to workspace r+1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace r+1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    NLog::log(\"{}Switching to workspace r~1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace r~1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 1 (1)\");\n    }\n\n    NLog::log(\"{}Switching to workspace empty\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace empty\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 2 (2)\");\n    }\n\n    NLog::log(\"{}Switching to workspace previous\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace previous\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 1 (1)\");\n    }\n\n    NLog::log(\"{}Switching to workspace name:TEST_WORKSPACE_NULL\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace name:TEST_WORKSPACE_NULL\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID -1337 (TEST_WORKSPACE_NULL)\");\n    }\n\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    // add a new monitor\n    NLog::log(\"{}Adding a new monitor\", Colors::YELLOW);\n    EXPECT(getFromSocket(\"/output create headless\"), \"ok\")\n\n    // should take workspace 2\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"active workspace: 2 (2)\");\n        EXPECT_CONTAINS(str, \"active workspace: 1 (1)\");\n        EXPECT_CONTAINS(str, \"HEADLESS-3\");\n    }\n\n    // focus the first monitor\n    OK(getFromSocket(\"/dispatch focusmonitor 0\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 1 (1)\");\n    }\n\n    NLog::log(\"{}Switching to workspace r+1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace r+1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    NLog::log(\"{}Switching to workspace r~2\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    OK(getFromSocket(\"/dispatch workspace r~2\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    NLog::log(\"{}Switching to workspace m+1\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch workspace m+1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 1 (1)\");\n    }\n\n    NLog::log(\"{}Switching to workspace 1\", Colors::YELLOW);\n    // no OK: this will throw an error as it should\n    getFromSocket(\"/dispatch workspace 1\");\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 1 (1)\");\n    }\n\n    NLog::log(\"{}Testing back_and_forth\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword binds:workspace_back_and_forth true\"));\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    OK(getFromSocket(\"/keyword binds:workspace_back_and_forth false\"));\n\n    NLog::log(\"{}Testing hide_special_on_workspace_change\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword binds:hide_special_on_workspace_change true\"));\n    OK(getFromSocket(\"/dispatch workspace special:HELLO\"));\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_CONTAINS(str, \"special workspace: -\");\n        EXPECT_CONTAINS(str, \"special:HELLO\");\n    }\n\n    // no OK: will err (it shouldn't prolly but oh well)\n    getFromSocket(\"/dispatch workspace 3\");\n\n    {\n        auto str = getFromSocket(\"/monitors\");\n        EXPECT_COUNT_STRING(str, \"special workspace: 0 ()\", 2);\n    }\n\n    OK(getFromSocket(\"/keyword binds:hide_special_on_workspace_change false\"));\n\n    NLog::log(\"{}Testing allow_workspace_cycles\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword binds:allow_workspace_cycles true\"));\n\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n    OK(getFromSocket(\"/dispatch workspace 3\"));\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    OK(getFromSocket(\"/dispatch workspace previous\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    OK(getFromSocket(\"/dispatch workspace previous\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 1 (1)\");\n    }\n\n    OK(getFromSocket(\"/dispatch workspace previous\"));\n\n    {\n        auto str = getFromSocket(\"/activeworkspace\");\n        EXPECT_STARTS_WITH(str, \"workspace ID 3 (3)\");\n    }\n\n    OK(getFromSocket(\"/keyword binds:allow_workspace_cycles false\"));\n\n    OK(getFromSocket(\"/dispatch workspace 1\"));\n\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    // spawn 3 kitties\n    NLog::log(\"{}Testing focus_preferred_method\", Colors::YELLOW);\n    OK(getFromSocket(\"/keyword dwindle:force_split 2\"));\n    Tests::spawnKitty(\"kitty_A\");\n    Tests::spawnKitty(\"kitty_B\");\n    Tests::spawnKitty(\"kitty_C\");\n    OK(getFromSocket(\"/keyword dwindle:force_split 0\"));\n\n    // focus kitty 2: will be top right (dwindle)\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_B\"));\n\n    // resize it to be a bit taller\n    OK(getFromSocket(\"/dispatch resizeactive +20 +20\"));\n\n    // now we test focus methods.\n    OK(getFromSocket(\"/keyword binds:focus_preferred_method 0\"));\n\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_C\"));\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_C\");\n    }\n\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n\n    OK(getFromSocket(\"/keyword binds:focus_preferred_method 1\"));\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_B\");\n    }\n\n    NLog::log(\"{}Testing movefocus_cycles_fullscreen\", Colors::YELLOW);\n    OK(getFromSocket(\"/dispatch focuswindow class:kitty_A\"));\n    OK(getFromSocket(\"/dispatch focusmonitor HEADLESS-3\"));\n    Tests::spawnKitty(\"kitty_D\");\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_D\");\n    }\n\n    OK(getFromSocket(\"/dispatch focusmonitor l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_A\");\n    }\n\n    OK(getFromSocket(\"/keyword binds:movefocus_cycles_fullscreen false\"));\n    OK(getFromSocket(\"/dispatch fullscreen 0\"));\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_D\");\n    }\n\n    OK(getFromSocket(\"/dispatch focusmonitor l\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_A\");\n    }\n\n    OK(getFromSocket(\"/keyword binds:movefocus_cycles_fullscreen true\"));\n\n    OK(getFromSocket(\"/dispatch movefocus r\"));\n\n    {\n        auto str = getFromSocket(\"/activewindow\");\n        EXPECT_CONTAINS(str, \"class: kitty_B\");\n    }\n\n    // kill all\n    NLog::log(\"{}Killing all windows\", Colors::YELLOW);\n    Tests::killAllWindows();\n\n    testMultimonBAF();\n    testMultimonFocus();\n\n    // destroy the headless output\n    OK(getFromSocket(\"/output remove HEADLESS-3\"));\n\n    testSpecialWorkspaceFullscreen();\n    testAsymmetricGaps();\n    testDynamicWsEffects();\n\n    NLog::log(\"{}Expecting 0 windows\", Colors::YELLOW);\n    EXPECT(Tests::windowCount(), 0);\n\n    return !ret;\n}\n\nREGISTER_TEST_FN(test)\n"
  },
  {
    "path": "hyprtester/src/tests/plugin/plugin.cpp",
    "content": "#include \"plugin.hpp\"\n#include \"../../shared.hpp\"\n#include \"../../hyprctlCompat.hpp\"\n#include <print>\n#include <thread>\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <csignal>\n#include <cerrno>\n#include \"../shared.hpp\"\n\nbool testPlugin() {\n    const auto RESPONSE = getFromSocket(\"/dispatch plugin:test:test\");\n\n    if (RESPONSE != \"ok\") {\n        NLog::log(\"{}Plugin tests failed, plugin returned:\\n{}{}\", Colors::RED, Colors::RESET, RESPONSE);\n        return false;\n    }\n    return true;\n}\n\nbool testVkb() {\n    const auto RESPONSE = getFromSocket(\"/dispatch plugin:test:vkb\");\n\n    if (RESPONSE != \"ok\") {\n        NLog::log(\"{}Vkb tests failed, tests returned:\\n{}{}\", Colors::RED, Colors::RESET, RESPONSE);\n        return false;\n    }\n    return true;\n}\n"
  },
  {
    "path": "hyprtester/src/tests/plugin/plugin.hpp",
    "content": "#pragma once\n\nbool testPlugin();\nbool testVkb();\n"
  },
  {
    "path": "hyprtester/src/tests/shared.cpp",
    "content": "#include \"shared.hpp\"\n#include <csignal>\n#include <cerrno>\n#include <thread>\n#include <print>\n#include <fstream>\n#include \"../shared.hpp\"\n#include \"../hyprctlCompat.hpp\"\n\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::Memory;\n\nCUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std::vector<std::string> args) {\n    const auto               COUNT_BEFORE = windowCount();\n\n    std::vector<std::string> programArgs = args;\n    if (!class_.empty()) {\n        programArgs.insert(programArgs.begin(), \"--class\");\n        programArgs.insert(programArgs.begin() + 1, class_);\n    }\n    CUniquePointer<CProcess> kitty = makeUnique<CProcess>(\"kitty\", programArgs);\n    kitty->addEnv(\"WAYLAND_DISPLAY\", WLDISPLAY);\n    kitty->runAsync();\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n\n    // wait while kitty spawns\n    int counter = 0;\n    while (processAlive(kitty->pid()) && windowCount() == COUNT_BEFORE) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50)\n            return nullptr;\n    }\n\n    if (!processAlive(kitty->pid()))\n        return nullptr;\n\n    return kitty;\n}\n\nCUniquePointer<CProcess> Tests::spawnLayerKitty(const std::string& namespace_, const std::vector<std::string> args) {\n    std::vector<std::string> programArgs = args;\n    if (!namespace_.empty()) {\n        programArgs.insert(programArgs.begin(), \"--class\");\n        programArgs.insert(programArgs.begin() + 1, namespace_);\n    }\n\n    programArgs.insert(programArgs.begin(), \"+kitten\");\n    programArgs.insert(programArgs.begin() + 1, \"panel\");\n\n    CUniquePointer<CProcess> kitty = makeUnique<CProcess>(\"kitty\", programArgs);\n    kitty->addEnv(\"WAYLAND_DISPLAY\", WLDISPLAY);\n    kitty->runAsync();\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n\n    // wait while the layer spawns\n    int counter = 0;\n    while (processAlive(kitty->pid()) && countOccurrences(getFromSocket(\"/layers\"), std::format(\"pid: {}\", kitty->pid())) == 0) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50)\n            return nullptr;\n    }\n\n    if (!processAlive(kitty->pid()))\n        return nullptr;\n\n    return kitty;\n}\n\nbool Tests::processAlive(pid_t pid) {\n    errno   = 0;\n    int ret = kill(pid, 0);\n    return ret != -1 || errno != ESRCH;\n}\n\nint Tests::windowCount() {\n    return countOccurrences(getFromSocket(\"/clients\"), \"focusHistoryID: \");\n}\n\nint Tests::countOccurrences(const std::string& in, const std::string& what) {\n    int  cnt = 0;\n    auto pos = in.find(what);\n    while (pos != std::string::npos) {\n        cnt++;\n        pos = in.find(what, pos + what.length());\n    }\n\n    return cnt;\n}\n\nbool Tests::killAllWindows() {\n    auto str = getFromSocket(\"/clients\");\n    auto pos = str.find(\"Window \");\n    while (pos != std::string::npos) {\n        auto pos2 = str.find(\" -> \", pos);\n        getFromSocket(\"/dispatch killwindow address:0x\" + str.substr(pos + 7, pos2 - pos - 7));\n        pos = str.find(\"Window \", pos + 5);\n    }\n\n    int counter = 0;\n    while (Tests::windowCount() != 0) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50) {\n            std::println(\"{}Timed out waiting for windows to close\", Colors::RED);\n            return false;\n        }\n    }\n\n    return true;\n}\n\nvoid Tests::waitUntilWindowsN(int n) {\n    int counter = 0;\n    while (Tests::windowCount() != n) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50) {\n            std::println(\"{}Timed out waiting for windows\", Colors::RED);\n            return;\n        }\n    }\n}\n\nint Tests::layerCount() {\n    return countOccurrences(getFromSocket(\"/layers\"), \"namespace: \");\n}\n\nbool Tests::killAllLayers() {\n    auto str = getFromSocket(\"/layers\");\n    auto pos = str.find(\"pid: \");\n    while (pos != std::string::npos) {\n        auto pid = stoi(str.substr(pos + 5, str.find('\\n', pos)));\n        kill(pid, 15);\n\n        // we need to wait for a bit because for some reason otherwise we'll end up\n        // with layers with pid -1 if they are all removed at the same time\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        pos = str.find(\"pid: \", pos + 5);\n    }\n\n    int counter = 0;\n    while (Tests::layerCount() != 0) {\n        counter++;\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n        if (counter > 50) {\n            std::println(\"{}Timed out waiting for layers to close\", Colors::RED);\n            return false;\n        }\n    }\n\n    return true;\n}\n\nstd::string Tests::execAndGet(const std::string& cmd) {\n    CProcess proc(\"/bin/sh\", {\"-c\", cmd});\n\n    if (!proc.runSync()) {\n        return \"error\";\n    }\n\n    return proc.stdOut();\n}\n\nbool Tests::writeFile(const std::string& name, const std::string& contents) {\n    std::ofstream of(name, std::ios::trunc);\n    if (!of.good())\n        return false;\n\n    of << contents;\n    of.close();\n\n    return true;\n}\n\nstd::string Tests::getWindowAttribute(const std::string& winInfo, const std::string& attr) {\n    auto pos = winInfo.find(attr);\n    if (pos == std::string::npos) {\n        NLog::log(\"{}Window attribute not found: '{}'\", Colors::RED, attr);\n        return \"Wrong window attribute\";\n    }\n    auto pos2 = winInfo.find('\\n', pos);\n    return winInfo.substr(pos, pos2 - pos);\n}\n"
  },
  {
    "path": "hyprtester/src/tests/shared.hpp",
    "content": "#pragma once\n\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <sys/types.h>\n\n#include \"../Log.hpp\"\n\n//NOLINTNEXTLINE\nnamespace Tests {\n    Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = \"\", const std::vector<std::string> args = {});\n    Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnLayerKitty(const std::string& namespace_ = \"\", const std::vector<std::string> args = {});\n    bool                                                       processAlive(pid_t pid);\n    int                                                        windowCount();\n    int                                                        countOccurrences(const std::string& in, const std::string& what);\n    bool                                                       killAllWindows();\n    void                                                       waitUntilWindowsN(int n);\n    int                                                        layerCount();\n    bool                                                       killAllLayers();\n    std::string                                                execAndGet(const std::string& cmd);\n    bool                                                       writeFile(const std::string& name, const std::string& contents);\n    std::string                                                getWindowAttribute(const std::string& winInfo, const std::string& attr);\n};\n"
  },
  {
    "path": "hyprtester/test.conf",
    "content": "# This is an example Hyprland config file.\n# Refer to the wiki for more information.\n# https://wiki.hyprland.org/Configuring/\n\n# Please note not all available settings / options are set here.\n# For a full list, see the wiki\n\n# You can split this configuration into multiple files\n# Create your files separately and then link them to this file like this:\n# source = ~/.config/hypr/myColors.conf\n\n\n################\n### MONITORS ###\n################\n\n# See https://wiki.hyprland.org/Configuring/Monitors/\n\nmonitor=HEADLESS-1,1920x1080@60,auto-right,1\nmonitor=HEADLESS-2,1920x1080@60,auto-right,1\nmonitor=HEADLESS-3,1920x1080@60,auto-right,1\nmonitor=HEADLESS-4,1920x1080@60,auto-right,1\nmonitor=HEADLESS-5,1920x1080@60,auto-right,1\nmonitor=HEADLESS-6,1920x1080@60,auto-right,1\nmonitor=HEADLESS-PERSISTENT-TEST,1920x1080@60,auto-right,1\n\nmonitor=,disabled\n\n\n###################\n### MY PROGRAMS ###\n###################\n\n# See https://wiki.hyprland.org/Configuring/Keywords/\n\n# Set programs that you use\n$terminal = kitty\n$fileManager = dolphin\n$menu = wofi --show drun\n\n\n#################\n### AUTOSTART ###\n#################\n\n# Autostart necessary processes (like notifications daemons, status bars, etc.)\n# Or execute your favorite apps at launch like this:\n\nexec-once = sleep 0  # Terminates very quickly\n# exec-once = $terminal\n# exec-once = nm-applet &\n# exec-once = waybar & hyprpaper & firefox\n\n\n#############################\n### ENVIRONMENT VARIABLES ###\n#############################\n\n# See https://wiki.hyprland.org/Configuring/Environment-variables/\n\nenv = XCURSOR_SIZE,24\nenv = HYPRCURSOR_SIZE,24\n\n\n#####################\n### LOOK AND FEEL ###\n#####################\n\n# Refer to https://wiki.hyprland.org/Configuring/Variables/\n\n# https://wiki.hyprland.org/Configuring/Variables/#general\ngeneral {\n    gaps_in = 5\n    gaps_out = 20\n\n    border_size = 2\n\n    snap {\n        enabled = true\n        window_gap = 8\n        monitor_gap = 10\n        respect_gaps = false\n        border_overlap = false\n    }\n\n    # https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors\n    col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg\n    col.inactive_border = rgba(595959aa)\n\n    # Set to true enable resizing windows by clicking and dragging on borders and gaps\n    resize_on_border = false\n\n    # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on\n    allow_tearing = false\n\n    layout = dwindle\n}\n\n# https://wiki.hyprland.org/Configuring/Variables/#decoration\ndecoration {\n    rounding = 10\n    rounding_power = 2\n\n    # Change transparency of focused and unfocused windows\n    active_opacity = 1.0\n    inactive_opacity = 1.0\n\n    shadow {\n        enabled = true\n        range = 4\n        render_power = 3\n        color = rgba(1a1a1aee)\n    }\n\n    # https://wiki.hyprland.org/Configuring/Variables/#blur\n    blur {\n        enabled = true\n        size = 3\n        passes = 1\n\n        vibrancy = 0.1696\n    }\n}\n\n# https://wiki.hyprland.org/Configuring/Variables/#animations\nanimations {\n    enabled = 0\n\n    # Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more\n\n    bezier = easeOutQuint,0.23,1,0.32,1\n    bezier = easeInOutCubic,0.65,0.05,0.36,1\n    bezier = linear,0,0,1,1\n    bezier = almostLinear,0.5,0.5,0.75,1.0\n    bezier = quick,0.15,0,0.1,1\n\n    animation = global, 1, 10, default\n    animation = border, 1, 5.39, easeOutQuint\n    animation = windows, 1, 4.79, easeOutQuint\n    animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%\n    animation = windowsOut, 1, 1.49, linear, popin 87%\n    animation = fadeIn, 1, 1.73, almostLinear\n    animation = fadeOut, 1, 1.46, almostLinear\n    animation = fade, 1, 3.03, quick\n    animation = layers, 1, 3.81, easeOutQuint\n    animation = layersIn, 1, 4, easeOutQuint, fade\n    animation = layersOut, 1, 1.5, linear, fade\n    animation = fadeLayersIn, 1, 1.79, almostLinear\n    animation = fadeLayersOut, 1, 1.39, almostLinear\n    animation = workspaces, 1, 1.94, almostLinear, fade\n    animation = workspacesIn, 1, 1.21, almostLinear, fade\n    animation = workspacesOut, 1, 1.94, almostLinear, fade\n}\n\ndevice {\n    name = test-mouse-1\n    enabled = true\n}\n\n# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/\n# \"Smart gaps\" / \"No gaps when only\"\n# uncomment all if you wish to use that.\n# workspace = w[tv1], gapsout:0, gapsin:0\n# workspace = f[1], gapsout:0, gapsin:0\n# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]\n# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]\n# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]\n# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]\n\n# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more\ndwindle {\n    pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below\n    preserve_split = true # You probably want this\n    split_bias = 1\n}\n\n# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more\nmaster {\n    new_status = master\n}\n\nscrolling {\n    fullscreen_on_one_column = true\n    column_width = 0.5\n    focus_fit_method = 1\n    follow_focus = true\n    follow_min_visible = 1\n    explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0\n    wrap_focus = true\n    wrap_swapcol = true\n}\n\n# https://wiki.hyprland.org/Configuring/Variables/#misc\nmisc {\n    force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers\n    disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(\n}\n\n\n#############\n### INPUT ###\n#############\n\n# https://wiki.hyprland.org/Configuring/Variables/#input\ninput {\n    kb_layout = us\n    kb_variant =\n    kb_model =\n    kb_options =\n    kb_rules =\n\n    follow_mouse = 1\n\n    sensitivity = 0 # -1.0 - 1.0, 0 means no modification.\n\n    touchpad {\n        natural_scroll = false\n    }\n}\n\n# https://wiki.hyprland.org/Configuring/Variables/#gestures\ngestures {\n\n}\n\n# Example per-device config\n# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more\ndevice {\n    name = epic-mouse-v1\n    sensitivity = -0.5\n}\n\ndebug {\n    disable_logs = false\n}\n\n\n###################\n### KEYBINDINGS ###\n###################\n\n# See https://wiki.hyprland.org/Configuring/Keywords/\n$mainMod = SUPER # Sets \"Windows\" key as main modifier\n\n# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more\nbind = $mainMod, Q, exec, $terminal\nbind = $mainMod, C, killactive,\nbind = $mainMod, M, exit,\nbind = $mainMod, E, exec, $fileManager\nbind = $mainMod, V, togglefloating,\nbind = $mainMod, R, exec, $menu\nbind = $mainMod, P, pseudo, # dwindle\nbind = $mainMod, J, layoutmsg, togglesplit, # dwindle\n\n# Move focus with mainMod + arrow keys\nbind = $mainMod, left, movefocus, l\nbind = $mainMod, right, movefocus, r\nbind = $mainMod, up, movefocus, u\nbind = $mainMod, down, movefocus, d\n\n# Switch workspaces with mainMod + [0-9]\nbind = $mainMod, 1, workspace, 1\nbind = $mainMod, 2, workspace, 2\nbind = $mainMod, 3, workspace, 3\nbind = $mainMod, 4, workspace, 4\nbind = $mainMod, 5, workspace, 5\nbind = $mainMod, 6, workspace, 6\nbind = $mainMod, 7, workspace, 7\nbind = $mainMod, 8, workspace, 8\nbind = $mainMod, 9, workspace, 9\nbind = $mainMod, 0, workspace, 10\n\n# Move active window to a workspace with mainMod + SHIFT + [0-9]\nbind = $mainMod SHIFT, 1, movetoworkspace, 1\nbind = $mainMod SHIFT, 2, movetoworkspace, 2\nbind = $mainMod SHIFT, 3, movetoworkspace, 3\nbind = $mainMod SHIFT, 4, movetoworkspace, 4\nbind = $mainMod SHIFT, 5, movetoworkspace, 5\nbind = $mainMod SHIFT, 6, movetoworkspace, 6\nbind = $mainMod SHIFT, 7, movetoworkspace, 7\nbind = $mainMod SHIFT, 8, movetoworkspace, 8\nbind = $mainMod SHIFT, 9, movetoworkspace, 9\nbind = $mainMod SHIFT, 0, movetoworkspace, 10\n\n# Example special workspace (scratchpad)\nbind = $mainMod, S, togglespecialworkspace, magic\nbind = $mainMod SHIFT, S, movetoworkspace, special:magic\n\n# Scroll through existing workspaces with mainMod + scroll\nbind = $mainMod, mouse_down, workspace, e+1\nbind = $mainMod, mouse_up, workspace, e-1\n\n# Move/resize windows with mainMod + LMB/RMB and dragging\nbindm = $mainMod, mouse:272, movewindow\nbindm = $mainMod, mouse:273, resizewindow\n\n# Laptop multimedia keys for volume and LCD brightness\nbindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+\nbindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-\nbindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle\nbindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle\nbindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+\nbindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-\n\n# Requires playerctl\nbindl = , XF86AudioNext, exec, playerctl next\nbindl = , XF86AudioPause, exec, playerctl play-pause\nbindl = , XF86AudioPlay, exec, playerctl play-pause\nbindl = , XF86AudioPrev, exec, playerctl previous\n\nbind = $mainMod, u, submap, submap1\n\nsubmap = submap1\nbind = , u, submap, submap2\nbind = , i, submap, submap3\nbind = , o, exec, $terminal\nbind = , p, submap, reset\n\nsubmap = submap2, submap1\nbind = , o, exec, $terminal\n\nsubmap = submap3, reset\nbind = , o, exec, $terminal\n\nsubmap = reset\n\n\n##############################\n### WINDOWS AND WORKSPACES ###\n##############################\n\nwindowrule {\n    # Ignore maximize requests from apps. You'll probably like this.\n    name = suppress-maximize-events\n    match:class = .*\n\n    suppress_event = maximize\n}\n\nwindowrule {\n    # Fix some dragging issues with XWayland\n    name = fix-xwayland-drags\n    match:class = ^$\n    match:title = ^$\n    match:xwayland = true\n    match:float = true\n    match:fullscreen = false\n    match:pin = false\n\n    no_focus = true\n}\n\nworkspace = n[s:window] w[tv1], gapsout:0, gapsin:0\nworkspace = n[s:window] f[1], gapsout:0, gapsin:0\n\nwindowrule {\n    name = smart-gaps-1\n    match:float = false\n    match:workspace = n[s:window] w[tv1]\n\n    border_size = 0\n    rounding = 0\n}\n\nwindowrule {\n    name = smart-gaps-2\n    match:float = false\n    match:workspace = n[s:window] f[1]\n\n    border_size = 0\n    rounding = 0\n}\n\nwindowrule {\n    name = wr-kitty-stuff\n    match:class = wr_kitty\n\n    float = true\n    size = 200 200\n    pin = false\n}\n\nwindowrule {\n    name = tagged-kitty-floats\n    match:tag = tag_kitty\n\n    float = true\n}\n\nwindowrule {\n    name = static-kitty-tag\n    match:class = tag_kitty\n\n    tag = +tag_kitty\n}\n\ngesture = 3, left, dispatcher, exec, kitty\ngesture = 3, right, float\ngesture = 3, up, close\ngesture = 3, down, fullscreen\n\ngesture = 3, down, mod:ALT, float\n\ngesture = 3, horizontal, mod:ALT, workspace\n\ngesture = 5, up, dispatcher, sendshortcut, , e, activewindow\ngesture = 5, down, dispatcher, sendshortcut, , x, activewindow\ngesture = 5, left, dispatcher, sendshortcut, , i, activewindow\ngesture = 5, right, dispatcher, sendshortcut, , t, activewindow\ngesture = 4, right, dispatcher, sendshortcut, , return, activewindow\ngesture = 4, left, dispatcher, movecursortocorner, 1\n\ngesturep = 2, right, float\n"
  },
  {
    "path": "nix/default.nix",
    "content": "{\n  lib,\n  stdenv,\n  stdenvAdapters,\n  pkg-config,\n  pkgconf,\n  makeWrapper,\n  cmake,\n  aquamarine,\n  binutils,\n  cairo,\n  epoll-shim,\n  git,\n  glaze-hyprland,\n  glslang,\n  gtest,\n  hyprcursor,\n  hyprgraphics,\n  hyprland-protocols,\n  hyprland-guiutils,\n  hyprlang,\n  hyprutils,\n  hyprwayland-scanner,\n  hyprwire,\n  lcms2,\n  libGL,\n  libdrm,\n  libexecinfo,\n  libinput,\n  libxcb,\n  libxcb-errors,\n  libxcb-render-util,\n  libxcb-wm,\n  libxdmcp,\n  libxcursor,\n  libxkbcommon,\n  libuuid,\n  libgbm,\n  muparser,\n  pango,\n  pciutils,\n  re2,\n  systemd,\n  tomlplusplus,\n  udis86-hyprland,\n  wayland,\n  wayland-protocols,\n  wayland-scanner,\n  xwayland,\n  debug ? false,\n  withTests ? debug,\n  enableXWayland ? true,\n  withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,\n  wrapRuntimeDeps ? true,\n  version ? \"git\",\n  commit,\n  revCount,\n  date,\n  # deprecated flags\n  enableNvidiaPatches ? false,\n  nvidiaPatches ? false,\n  hidpiXWayland ? false,\n  legacyRenderer ? false,\n  withHyprtester ? false,\n}:\nlet\n  inherit (builtins) foldl' readFile;\n  inherit (lib.asserts) assertMsg;\n  inherit (lib.attrsets) mapAttrsToList;\n  inherit (lib.lists)\n    flatten\n    concatLists\n    optional\n    optionals\n    ;\n  inherit (lib.strings)\n    makeBinPath\n    optionalString\n    cmakeBool\n    trim\n    ;\n  fs = lib.fileset;\n\n  adapters = flatten [\n    stdenvAdapters.useMoldLinker\n    (lib.optional debug stdenvAdapters.keepDebugInfo)\n  ];\n\n  customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;\nin\nassert assertMsg (!nvidiaPatches) \"The option `nvidiaPatches` has been removed.\";\nassert assertMsg (!enableNvidiaPatches) \"The option `enableNvidiaPatches` has been removed.\";\nassert assertMsg (!hidpiXWayland)\n  \"The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland\";\nassert assertMsg (\n  !legacyRenderer\n) \"The option `legacyRenderer` has been removed. Legacy renderer is no longer supported.\";\nassert assertMsg (\n  !withHyprtester\n) \"The option `withHyprtester` has been removed. Hyprtester is always built now.\";\ncustomStdenv.mkDerivation (finalAttrs: {\n  pname = \"hyprland${optionalString debug \"-debug\"}\";\n  inherit version withTests;\n\n  src = fs.toSource {\n    root = ../.;\n    fileset =\n      fs.intersection\n        # allows non-flake builds to only include files tracked by git\n        (fs.gitTracked ../.)\n        (\n          fs.unions (flatten [\n            ../assets/hyprland-portals.conf\n            ../assets/install\n            ../hyprctl\n            ../hyprland.pc.in\n            ../hyprpm\n            ../LICENSE\n            ../protocols\n            ../src\n            ../start\n            ../systemd\n            ../VERSION\n            (fs.fileFilter (file: file.hasExt \"1\") ../docs)\n            (fs.fileFilter (file: file.hasExt \"conf\" || file.hasExt \"in\") ../example)\n            (fs.fileFilter (file: file.hasExt \"sh\") ../scripts)\n            (fs.fileFilter (file: file.name == \"CMakeLists.txt\") ../.)\n            (optional withTests [\n              ../tests\n              ../hyprtester\n            ])\n          ])\n        );\n  };\n\n  postPatch = ''\n    # Fix hardcoded paths to /usr installation\n    sed -i \"s#/usr#$out#\" src/render/OpenGL.cpp\n\n    # Remove extra @PREFIX@ to fix some paths\n    sed -i \"s#@PREFIX@/##g\" hyprland.pc.in\n    sed -i \"s#@PREFIX@/##g\" example/hyprland.desktop.in\n  '';\n\n  env = {\n    GIT_COMMITS = revCount;\n    GIT_COMMIT_DATE = date;\n    GIT_COMMIT_HASH = commit;\n    GIT_DIRTY = if (commit == \"\") then \"clean\" else \"dirty\";\n    GIT_TAG = \"v${trim (readFile \"${finalAttrs.src}/VERSION\")}\";\n  };\n\n  depsBuildBuild = [\n    pkg-config\n  ];\n\n  nativeBuildInputs = [\n    hyprwayland-scanner\n    hyprwire\n    makeWrapper\n    cmake\n    pkg-config\n  ];\n\n  outputs = [\n    \"out\"\n    \"man\"\n    \"dev\"\n  ];\n\n  buildInputs = concatLists [\n    [\n      aquamarine\n      cairo\n      git\n      glaze-hyprland\n      glslang\n      gtest\n      hyprcursor\n      hyprgraphics\n      hyprland-protocols\n      hyprlang\n      hyprutils\n      hyprwire\n      lcms2\n      libdrm\n      libgbm\n      libGL\n      libinput\n      libuuid\n      libxcursor\n      libxkbcommon\n      muparser\n      pango\n      pciutils\n      re2\n      tomlplusplus\n      udis86-hyprland\n      wayland\n      wayland-protocols\n      wayland-scanner\n    ]\n    (optionals customStdenv.hostPlatform.isBSD [ epoll-shim ])\n    (optionals customStdenv.hostPlatform.isMusl [ libexecinfo ])\n    (optionals enableXWayland [\n      libxcb\n      libxcb-errors\n      libxcb-render-util\n      libxcb-wm\n      libxdmcp\n      xwayland\n    ])\n    (optional withSystemd systemd)\n  ];\n\n  strictDeps = true;\n\n  cmakeBuildType = if debug then \"Debug\" else \"RelWithDebInfo\";\n\n  # we want as much debug info as possible\n  dontStrip = debug;\n\n  cmakeFlags = mapAttrsToList cmakeBool {\n    \"BUILT_WITH_NIX\" = true;\n    \"NO_XWAYLAND\" = !enableXWayland;\n    \"LEGACY_RENDERER\" = legacyRenderer;\n    \"NO_SYSTEMD\" = !withSystemd;\n    \"CMAKE_DISABLE_PRECOMPILE_HEADERS\" = true;\n    \"NO_UWSM\" = !withSystemd;\n    \"TRACY_ENABLE\" = false;\n    \"WITH_TESTS\" = withTests;\n  };\n\n  preConfigure = ''\n    substituteInPlace hyprtester/CMakeLists.txt --replace-fail \\\n      \"\\''${CMAKE_CURRENT_BINARY_DIR}\" \\\n      \"${placeholder \"out\"}/bin\"\n  '';\n\n  postInstall = ''\n    ${optionalString wrapRuntimeDeps ''\n      wrapProgram $out/bin/Hyprland \\\n        --suffix PATH : ${\n          makeBinPath [\n            binutils\n            hyprland-guiutils\n            pciutils\n            pkgconf\n          ]\n        }\n    ''}\n\n    ${optionalString withTests ''\n      install hyprtester/pointer-warp -t $out/bin\n      install hyprtester/pointer-scroll -t $out/bin\n      install hyprtester/shortcut-inhibitor -t $out/bin\n      install hyprland_gtests -t $out/bin\n      install hyprtester/child-window -t $out/bin\n    ''}\n  '';\n\n  passthru.providedSessions = [ \"hyprland\" ] ++ optionals withSystemd [ \"hyprland-uwsm\" ];\n\n  meta = {\n    homepage = \"https://github.com/hyprwm/Hyprland\";\n    description = \"Dynamic tiling Wayland compositor that doesn't sacrifice on its looks\";\n    license = lib.licenses.bsd3;\n    platforms = lib.platforms.linux;\n    mainProgram = \"Hyprland\";\n  };\n})\n"
  },
  {
    "path": "nix/formatter.nix",
    "content": "{\n  writeShellApplication,\n  deadnix,\n  statix,\n  nixfmt,\n  llvmPackages_19,\n  fd,\n}:\nwriteShellApplication {\n  name = \"hyprland-treewide-formatter\";\n  runtimeInputs = [\n    deadnix\n    statix\n    nixfmt\n    llvmPackages_19.clang-tools\n    fd\n  ];\n  text = ''\n    # shellcheck disable=SC2148\n\n    # common excludes\n    excludes=\"subprojects\"\n\n    nix_format() {\n      if [ \"$*\" = 0 ]; then\n        fd '.*\\.nix' . -E \"$excludes\" -x statix fix -- {} \\;\n        fd '.*\\.nix' . -E \"$excludes\" -X deadnix -e -- {} \\; -X nixfmt {} \\;\n      elif [ -d \"$1\" ]; then\n        fd '.*\\.nix' \"$1\" -E \"$excludes\" -i -x statix fix -- {} \\;\n        fd '.*\\.nix' \"$1\" -E \"$excludes\" -i -X deadnix -e -- {} \\; -X nixfmt {} \\;\n      else\n        statix fix -- \"$1\"\n        deadnix -e \"$1\"\n        nixfmt \"$1\"\n      fi\n    }\n\n    cpp_format() {\n      if [ \"$*\" = 0 ] || [ \"$1\" = \".\" ]; then\n        fd '.*\\.cpp' . -E \"$excludes\"  | xargs clang-format --verbose -i\n      elif [ -d \"$1\" ]; then\n        fd '.*\\.cpp' \"$1\" -E \"$excludes\" | xargs clang-format --verbose -i\n      else\n        clang-format --verbose -i \"$1\"\n      fi\n    }\n\n    for i in \"$@\"; do\n      case ''${i##*.} in\n        \"nix\")\n          nix_format \"$i\"\n          ;;\n        \"cpp\")\n          cpp_format \"$i\"\n          ;;\n        *)\n          nix_format \"$i\"\n          cpp_format \"$i\"\n          ;;\n      esac\n\n    done\n  '';\n}\n"
  },
  {
    "path": "nix/hm-module.nix",
    "content": "self:\n{\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  inherit (pkgs.stdenv.hostPlatform) system;\n\n  package = self.packages.${system}.default;\nin\n{\n  config = {\n    wayland.windowManager.hyprland.package = lib.mkDefault package;\n  };\n}\n"
  },
  {
    "path": "nix/lib.nix",
    "content": "lib:\nlet\n  inherit (lib)\n    attrNames\n    filterAttrs\n    foldl\n    generators\n    partition\n    ;\n\n  inherit (lib.strings)\n    concatMapStrings\n    hasPrefix\n    ;\n\n  /**\n    Convert a structured Nix attribute set into Hyprland's configuration format.\n\n    This function takes a nested attribute set and converts it into Hyprland-compatible\n    configuration syntax, supporting top, bottom, and regular command sections.\n\n    Commands are flattened using the `flattenAttrs` function, and attributes are formatted as\n    `key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.\n\n    Configuration:\n\n    * `topCommandsPrefixes` - A list of prefixes to define **top** commands (default: `[\"$\"]`).\n    * `bottomCommandsPrefixes` - A list of prefixes to define **bottom** commands (default: `[]`).\n\n    Attention:\n\n    - The function ensures top commands appear **first** and bottom commands **last**.\n    - The generated configuration is a **single string**, suitable for writing to a config file.\n    - Lists are converted into multiple entries, ensuring compatibility with Hyprland.\n\n    # Inputs\n\n    Structured function argument:\n\n    : topCommandsPrefixes (optional, default: `[\"$\"]`)\n      : A list of prefixes that define **top** commands. Any key starting with one of these\n        prefixes will be placed at the beginning of the configuration.\n    : bottomCommandsPrefixes (optional, default: `[]`)\n      : A list of prefixes that define **bottom** commands. Any key starting with one of these\n        prefixes will be placed at the end of the configuration.\n\n    Value:\n\n    : The attribute set to be converted to Hyprland configuration format.\n\n    # Type\n\n    ```\n    toHyprlang :: AttrSet -> AttrSet -> String\n    ```\n\n    # Examples\n    :::{.example}\n\n    ```nix\n    let\n      config = {\n        \"$mod\" = \"SUPER\";\n        monitor = {\n          \"HDMI-A-1\" = \"1920x1080@60,0x0,1\";\n        };\n        exec = [\n          \"waybar\"\n          \"dunst\"\n        ];\n      };\n    in lib.toHyprlang {} config\n    ```\n\n    **Output:**\n    ```nix\n    \"$mod = SUPER\"\n    \"monitor:HDMI-A-1 = 1920x1080@60,0x0,1\"\n    \"exec = waybar\"\n    \"exec = dunst\"\n    ```\n\n    :::\n  */\n  toHyprlang =\n    {\n      topCommandsPrefixes ? [\n        \"$\"\n        \"bezier\"\n      ],\n      bottomCommandsPrefixes ? [ ],\n    }:\n    attrs:\n    let\n      toHyprlang' =\n        attrs:\n        let\n          # Specially configured `toKeyValue` generator with support for duplicate keys\n          # and a legible key-value separator.\n          mkCommands = generators.toKeyValue {\n            mkKeyValue = generators.mkKeyValueDefault { } \" = \";\n            listsAsDuplicateKeys = true;\n            indent = \"\"; # No indent, since we don't have nesting\n          };\n\n          # Flatten the attrset, combining keys in a \"path\" like `\"a:b:c\" = \"x\"`.\n          # Uses `flattenAttrs` with a colon separator.\n          commands = flattenAttrs (p: k: \"${p}:${k}\") attrs;\n\n          # General filtering function to check if a key starts with any prefix in a given list.\n          filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list;\n\n          # Partition keys into top commands and the rest\n          result = partition (filterCommands topCommandsPrefixes) (attrNames commands);\n          topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;\n          remainingCommands = removeAttrs commands result.right;\n\n          # Partition remaining commands into bottom commands and regular commands\n          result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;\n          bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;\n          regularCommands = removeAttrs remainingCommands result2.right;\n        in\n        # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.\n        concatMapStrings mkCommands [\n          topCommands\n          regularCommands\n          bottomCommands\n        ];\n    in\n    toHyprlang' attrs;\n\n  /**\n    Flatten a nested attribute set into a flat attribute set, using a custom key separator function.\n\n    This function recursively traverses a nested attribute set and produces a flat attribute set\n    where keys are joined using a user-defined function (`pred`). It allows transforming deeply\n    nested structures into a single-level attribute set while preserving key-value relationships.\n\n    Configuration:\n\n    * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.\n\n    # Inputs\n\n    Structured function argument:\n\n    : pred (required)\n      : A function that determines how parent and child keys should be combined into a single key.\n        It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.\n\n    Value:\n\n    : The nested attribute set to be flattened.\n\n    # Type\n\n    ```\n    flattenAttrs :: (String -> String -> String) -> AttrSet -> AttrSet\n    ```\n\n    # Examples\n    :::{.example}\n\n    ```nix\n    let\n      nested = {\n        a = \"3\";\n        b = { c = \"4\"; d = \"5\"; };\n      };\n\n      separator = (prefix: key: \"${prefix}.${key}\");  # Use dot notation\n    in lib.flattenAttrs separator nested\n    ```\n\n    **Output:**\n    ```nix\n    {\n      \"a\" = \"3\";\n      \"b.c\" = \"4\";\n      \"b.d\" = \"5\";\n    }\n    ```\n\n    :::\n  */\n  flattenAttrs =\n    pred: attrs:\n    let\n      flattenAttrs' =\n        prefix: attrs:\n        builtins.foldl' (\n          acc: key:\n          let\n            value = attrs.${key};\n            newKey = if prefix == \"\" then key else pred prefix key;\n          in\n          acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { \"${newKey}\" = value; })\n        ) { } (builtins.attrNames attrs);\n    in\n    flattenAttrs' \"\" attrs;\nin\n{\n  inherit flattenAttrs toHyprlang;\n}\n"
  },
  {
    "path": "nix/module.nix",
    "content": "inputs:\n{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  inherit (pkgs.stdenv.hostPlatform) system;\n  selflib = import ./lib.nix lib;\n  cfg = config.programs.hyprland;\nin\n{\n  options = {\n    programs.hyprland = {\n      plugins = lib.mkOption {\n        type = with lib.types; listOf (either package path);\n        default = [ ];\n        description = ''\n          List of Hyprland plugins to use. Can either be packages or\n          absolute plugin paths.\n        '';\n      };\n\n      settings = lib.mkOption {\n        type =\n          with lib.types;\n          let\n            valueType =\n              nullOr (oneOf [\n                bool\n                int\n                float\n                str\n                path\n                (attrsOf valueType)\n                (listOf valueType)\n              ])\n              // {\n                description = \"Hyprland configuration value\";\n              };\n          in\n          valueType;\n        default = { };\n        description = ''\n          Hyprland configuration written in Nix. Entries with the same key\n          should be written as lists. Variables' and colors' names should be\n          quoted. See <https://wiki.hypr.land> for more examples.\n\n          Special categories (e.g `devices`) should be written as\n          `\"devices[device-name]\"`.\n\n          ::: {.note}\n          Use the [](#programs.hyprland.plugins) option to\n          declare plugins.\n          :::\n\n        '';\n        example = lib.literalExpression ''\n          {\n            decoration = {\n              shadow_offset = \"0 5\";\n              \"col.shadow\" = \"rgba(00000099)\";\n            };\n\n            \"$mod\" = \"SUPER\";\n\n            bindm = [\n              # mouse movements\n              \"$mod, mouse:272, movewindow\"\n              \"$mod, mouse:273, resizewindow\"\n              \"$mod ALT, mouse:272, resizewindow\"\n            ];\n          }\n        '';\n      };\n\n      extraConfig = lib.mkOption {\n        type = lib.types.lines;\n        default = \"\";\n        example = ''\n          # window resize\n          bind = $mod, S, submap, resize\n\n          submap = resize\n          binde = , right, resizeactive, 10 0\n          binde = , left, resizeactive, -10 0\n          binde = , up, resizeactive, 0 -10\n          binde = , down, resizeactive, 0 10\n          bind = , escape, submap, reset\n          submap = reset\n        '';\n        description = ''\n          Extra configuration lines to add to `/etc/xdg/hypr/hyprland.conf`.\n        '';\n      };\n\n      topPrefixes = lib.mkOption {\n        type = with lib.types; listOf str;\n        default = [\n          \"$\"\n          \"bezier\"\n        ];\n        example = [\n          \"$\"\n          \"bezier\"\n          \"source\"\n        ];\n        description = ''\n          List of prefix of attributes to put at the top of the config.\n        '';\n      };\n\n      bottomPrefixes = lib.mkOption {\n        type = with lib.types; listOf str;\n        default = [ ];\n        example = [ \"source\" ];\n        description = ''\n          List of prefix of attributes to put at the bottom of the config.\n        '';\n      };\n    };\n  };\n  config = lib.mkMerge [\n    {\n      programs.hyprland = {\n        package = lib.mkDefault inputs.self.packages.${system}.hyprland;\n        portalPackage = lib.mkDefault inputs.self.packages.${system}.xdg-desktop-portal-hyprland;\n      };\n    }\n    (lib.mkIf cfg.enable {\n      environment.etc.\"xdg/hypr/hyprland.conf\" =\n        let\n          shouldGenerate = cfg.extraConfig != \"\" || cfg.settings != { } || cfg.plugins != [ ];\n\n          pluginsToHyprlang =\n            _plugins:\n            selflib.toHyprlang\n              {\n                topCommandsPrefixes = cfg.topPrefixes;\n                bottomCommandsPrefixes = cfg.bottomPrefixes;\n              }\n              {\n                \"exec-once\" =\n                  let\n                    mkEntry =\n                      entry: if lib.types.package.check entry then \"${entry}/lib/lib${entry.pname}.so\" else entry;\n                    hyprctl = lib.getExe' config.programs.hyprland.package \"hyprctl\";\n                  in\n                  map (p: \"${hyprctl} plugin load ${mkEntry p}\") cfg.plugins;\n              };\n        in\n        lib.mkIf shouldGenerate {\n          text =\n            lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins)\n            + lib.optionalString (cfg.settings != { }) (\n              selflib.toHyprlang {\n                topCommandsPrefixes = cfg.topPrefixes;\n                bottomCommandsPrefixes = cfg.bottomPrefixes;\n              } cfg.settings\n            )\n            + lib.optionalString (cfg.extraConfig != \"\") cfg.extraConfig;\n        };\n    })\n  ];\n}\n"
  },
  {
    "path": "nix/overlays.nix",
    "content": "{\n  self,\n  lib,\n  inputs,\n}:\nlet\n  mkDate =\n    longDate:\n    (lib.concatStringsSep \"-\" [\n      (builtins.substring 0 4 longDate)\n      (builtins.substring 4 2 longDate)\n      (builtins.substring 6 2 longDate)\n    ]);\n  ver = lib.removeSuffix \"\\n\" (builtins.readFile ../VERSION);\nin\n{\n  # Contains what a user is most likely to care about:\n  # Hyprland itself, XDPH and the Share Picker.\n  default = lib.composeManyExtensions (\n    with self.overlays;\n    [\n      hyprland-packages\n      hyprland-extras\n    ]\n  );\n\n  # Packages for variations of Hyprland, dependencies included.\n  hyprland-packages = lib.composeManyExtensions [\n    # Dependencies\n    inputs.aquamarine.overlays.default\n    inputs.hyprcursor.overlays.default\n    inputs.hyprgraphics.overlays.default\n    inputs.hyprland-protocols.overlays.default\n    inputs.hyprland-guiutils.overlays.default\n    inputs.hyprlang.overlays.default\n    inputs.hyprutils.overlays.default\n    inputs.hyprwayland-scanner.overlays.default\n    inputs.hyprwire.overlays.default\n    self.overlays.udis86\n    self.overlays.glaze\n\n    # Hyprland packages themselves\n    (\n      final: _prev:\n      let\n        date = mkDate (self.lastModifiedDate or \"19700101\");\n        version = \"${ver}+date=${date}_${self.shortRev or \"dirty\"}\";\n      in\n      {\n        hyprland = final.callPackage ./default.nix {\n          stdenv = final.gcc15Stdenv;\n          commit = self.rev or \"\";\n          revCount = self.sourceInfo.revCount or \"\";\n          inherit date version;\n        };\n        hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; };\n\n        hyprland-with-tests = final.hyprland.override { withTests = true; };\n\n        hyprland-with-hyprtester = builtins.trace ''\n          hyprland-with-hyprtester was removed. Please use the hyprland package.\n          Hyprtester is always built now.\n        '' final.hyprland;\n\n        # deprecated packages\n        hyprland-legacy-renderer = builtins.trace ''\n          hyprland-legacy-renderer was removed. Please use the hyprland package.\n          Legacy renderer is no longer supported.\n        '' final.hyprland;\n\n        hyprland-nvidia = builtins.trace ''\n          hyprland-nvidia was removed. Please use the hyprland package.\n          Nvidia patches are no longer needed.\n        '' final.hyprland;\n\n        hyprland-hidpi = builtins.trace ''\n          hyprland-hidpi was removed. Please use the hyprland package.\n          For more information, refer to https://wiki.hypr.land/Configuring/XWayland.\n        '' final.hyprland;\n      }\n    )\n  ];\n\n  # Debug\n  hyprland-debug = lib.composeManyExtensions [\n    # Dependencies\n    self.overlays.hyprland-packages\n\n    (_final: prev: {\n      aquamarine = prev.aquamarine.override { debug = true; };\n      hyprutils = prev.hyprutils.override { debug = true; };\n      hyprland-debug = prev.hyprland.override { debug = true; };\n    })\n  ];\n\n  # Packages for extra software recommended for usage with Hyprland,\n  # including forked or patched packages for compatibility.\n  hyprland-extras = lib.composeManyExtensions [\n    inputs.xdph.overlays.default\n  ];\n\n  # udis86 from nixpkgs is too old, and also does not provide a .pc file\n  # this version is the one used in the git submodule, and allows us to\n  # fetch the source without '?submodules=1'\n  udis86 = final: prev: {\n    udis86-hyprland = prev.udis86.overrideAttrs (\n      _self: _super: {\n        src = final.fetchFromGitHub {\n          owner = \"canihavesomecoffee\";\n          repo = \"udis86\";\n          rev = \"5336633af70f3917760a6d441ff02d93477b0c86\";\n          hash = \"sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=\";\n        };\n\n        patches = [ ];\n      }\n    );\n  };\n\n  # Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true.\n  # Since we don't include openssl, the build failes without the `enableSSL = false;` override\n  glaze = _final: prev: {\n    glaze-hyprland = prev.glaze.override {\n      enableSSL = false;\n      enableInterop = false;\n    };\n  };\n}\n"
  },
  {
    "path": "nix/tests/default.nix",
    "content": "inputs: pkgs:\nlet\n  flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};\n  hyprland = flake.hyprland-with-tests;\nin\n{\n  tests = pkgs.testers.runNixOSTest {\n    name = \"hyprland-tests\";\n\n    nodes.machine =\n      { pkgs, ... }:\n      {\n        environment.systemPackages = with pkgs; [\n          # Programs needed for tests\n          jq\n          kitty\n          wl-clipboard\n          xeyes\n        ];\n\n        # Enabled by default for some reason\n        services.speechd.enable = false;\n\n        environment.variables = {\n          \"AQ_TRACE\" = \"1\";\n          \"HYPRLAND_TRACE\" = \"1\";\n          \"XDG_RUNTIME_DIR\" = \"/tmp\";\n          \"XDG_CACHE_HOME\" = \"/tmp\";\n          \"KITTY_CONFIG_DIRECTORY\" = \"/etc/kitty\";\n        };\n\n        environment.etc.\"kitty/kitty.conf\".text = ''\n          confirm_os_window_close 0\n          remember_window_size no\n          initial_window_width  640\n          initial_window_height 400\n        '';\n\n        programs.hyprland = {\n          enable = true;\n          package = hyprland;\n          # We don't need portals in this test, so we don't set portalPackage\n        };\n\n        # Test configuration\n        environment.etc.\"test.conf\".source = \"${hyprland}/share/hypr/test.conf\";\n\n        # Disable portals\n        xdg.portal.enable = pkgs.lib.mkForce false;\n\n        # Autologin root into tty\n        services.getty.autologinUser = \"alice\";\n\n        system.stateVersion = \"24.11\";\n\n        users.users.alice = {\n          isNormalUser = true;\n        };\n\n        virtualisation = {\n          cores = 4;\n          # Might crash with less\n          memorySize = 8192;\n          resolution = {\n            x = 1920;\n            y = 1080;\n          };\n\n          # Doesn't seem to do much, thought it would fix XWayland crashing\n          qemu.options = [ \"-vga none -device virtio-gpu-pci\" ];\n        };\n      };\n\n    testScript = ''\n      # Wait for tty to be up\n      machine.wait_for_unit(\"multi-user.target\")\n\n\n      # Run gtests\n      print(\"Running gtests\")\n      exit_status, _out = machine.execute(\"su - alice -c 'hyprland_gtests 2>&1 | tee /tmp/gtestslog; exit ''${PIPESTATUS[0]}'\")\n      machine.execute(f'echo {exit_status} > /tmp/exit_status_gtests')\n\n      # Run hyprtester testing framework/suite\n      print(\"Running hyprtester\")\n      exit_status, _out = machine.execute(\"su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'\")\n      print(f\"Hyprtester exited with {exit_status}\")\n\n      # Copy logs to host\n      machine.execute('cp \"$(find /tmp/hypr -name *.log | head -1)\" /tmp/hyprlog')\n      machine.execute(f'echo {exit_status} > /tmp/exit_status')\n      machine.copy_from_vm(\"/tmp/gtestslog\")\n      machine.copy_from_vm(\"/tmp/testerlog\")\n      machine.copy_from_vm(\"/tmp/hyprlog\")\n      machine.copy_from_vm(\"/tmp/exit_status\")\n\n      # Print logs for visibility in CI\n      _, out = machine.execute(\"cat /tmp/testerlog\")\n      print(f\"Hyprtester log:\\n{out}\")\n\n      # Finally - shutdown\n      machine.shutdown()\n    '';\n  };\n}\n"
  },
  {
    "path": "nix/update-inputs.sh",
    "content": "#!/usr/bin/env -S nix shell nixpkgs#jq -c bash\n\n# Update inputs when the Mesa or QT version is outdated. We don't want\n# incompatibilities between the user's system and Hyprland.\n\n# get the current Nixpkgs revision\nREV=$(jq <flake.lock '.nodes.nixpkgs.locked.rev' -r)\n\nget_ver() {\n  nix eval --raw \"github:nixos/nixpkgs/$1#$2\"\n}\n\n# check versions for current and remote nixpkgs'\nMESA_OLD=$(get_ver \"$REV\" mesa.version)\nMESA_NEW=$(get_ver nixos-unstable mesa.version)\nQT_OLD=$(get_ver \"$REV\" kdePackages.qtbase.version)\nQT_NEW=$(get_ver nixos-unstable kdePackages.qtbase.version)\n\nif [ \"$MESA_OLD\" != \"$MESA_NEW\" ] || [ \"$QT_OLD\" != \"$QT_NEW\" ]; then\n  echo \"Updating flake inputs...\"\n  echo \"Mesa: $MESA_OLD -> $MESA_NEW\"\n  echo \"Qt:   $QT_OLD -> $QT_NEW\"\n\n  # update inputs to latest versions\n  nix flake update\nelse\n  echo \"nixpkgs is up to date!\"\nfi\n"
  },
  {
    "path": "protocols/input-method-unstable-v2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"input_method_unstable_v2\">\n\n  <copyright>\n    Copyright © 2008-2011 Kristian Høgsberg\n    Copyright © 2010-2011 Intel Corporation\n    Copyright © 2012-2013 Collabora, Ltd.\n    Copyright © 2012, 2013 Intel Corporation\n    Copyright © 2015, 2016 Jan Arne Petersen\n    Copyright © 2017, 2018 Red Hat, Inc.\n    Copyright © 2018       Purism SPC\n\n    Permission is hereby granted, free of charge, to any person obtaining a\n    copy of this software and associated documentation files (the \"Software\"),\n    to deal in the Software without restriction, including without limitation\n    the rights to use, copy, modify, merge, publish, distribute, sublicense,\n    and/or sell copies of the Software, and to permit persons to whom the\n    Software is furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice (including the next\n    paragraph) shall be included in all copies or substantial portions of the\n    Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n    DEALINGS IN THE SOFTWARE.\n  </copyright>\n\n  <description summary=\"Protocol for creating input methods\">\n    This protocol allows applications to act as input methods for compositors.\n\n    An input method context is used to manage the state of the input method.\n\n    Text strings are UTF-8 encoded, their indices and lengths are in bytes.\n\n    This document adheres to the RFC 2119 when using words like \"must\",\n    \"should\", \"may\", etc.\n\n    Warning! The protocol described in this file is experimental and\n    backward incompatible changes may be made. Backward compatible changes\n    may be added together with the corresponding interface version bump.\n    Backward incompatible changes are done by bumping the version number in\n    the protocol and interface names and resetting the interface version.\n    Once the protocol is to be declared stable, the 'z' prefix and the\n    version number in the protocol and interface names are removed and the\n    interface version number is reset.\n  </description>\n\n  <interface name=\"zwp_input_method_v2\" version=\"1\">\n    <description summary=\"input method\">\n      An input method object allows for clients to compose text.\n\n      The objects connects the client to a text input in an application, and\n      lets the client to serve as an input method for a seat.\n\n      The zwp_input_method_v2 object can occupy two distinct states: active and\n      inactive. In the active state, the object is associated to and\n      communicates with a text input. In the inactive state, there is no\n      associated text input, and the only communication is with the compositor.\n      Initially, the input method is in the inactive state.\n\n      Requests issued in the inactive state must be accepted by the compositor.\n      Because of the serial mechanism, and the state reset on activate event,\n      they will not have any effect on the state of the next text input.\n\n      There must be no more than one input method object per seat.\n    </description>\n\n    <enum name=\"error\">\n      <entry name=\"role\" value=\"0\" summary=\"wl_surface has another role\"/>\n    </enum>\n\n    <event name=\"activate\">\n      <description summary=\"input method has been requested\">\n        Notification that a text input focused on this seat requested the input\n        method to be activated.\n\n        This event serves the purpose of providing the compositor with an\n        active input method.\n\n        This event resets all state associated with previous enable, disable,\n        surrounding_text, text_change_cause, and content_type events, as well\n        as the state associated with set_preedit_string, commit_string, and\n        delete_surrounding_text requests. In addition, it marks the\n        zwp_input_method_v2 object as active, and makes any existing\n        zwp_input_popup_surface_v2 objects visible.\n\n        The surrounding_text, and content_type events must follow before the\n        next done event if the text input supports the respective\n        functionality.\n\n        State set with this event is double-buffered. It will get applied on\n        the next zwp_input_method_v2.done event, and stay valid until changed.\n      </description>\n    </event>\n\n    <event name=\"deactivate\">\n      <description summary=\"deactivate event\">\n        Notification that no focused text input currently needs an active \n        input method on this seat.\n\n        This event marks the zwp_input_method_v2 object as inactive. The\n        compositor must make all existing zwp_input_popup_surface_v2 objects\n        invisible until the next activate event.\n\n        State set with this event is double-buffered. It will get applied on\n        the next zwp_input_method_v2.done event, and stay valid until changed.\n      </description>\n    </event>\n\n    <event name=\"surrounding_text\">\n      <description summary=\"surrounding text event\">\n        Updates the surrounding plain text around the cursor, excluding the\n        preedit text.\n\n        If any preedit text is present, it is replaced with the cursor for the\n        purpose of this event.\n\n        The argument text is a buffer containing the preedit string, and must\n        include the cursor position, and the complete selection. It should\n        contain additional characters before and after these. There is a\n        maximum length of wayland messages, so text can not be longer than 4000\n        bytes.\n\n        cursor is the byte offset of the cursor within the text buffer.\n\n        anchor is the byte offset of the selection anchor within the text\n        buffer. If there is no selected text, anchor must be the same as\n        cursor.\n\n        If this event does not arrive before the first done event, the input\n        method may assume that the text input does not support this\n        functionality and ignore following surrounding_text events.\n\n        Values set with this event are double-buffered. They will get applied\n        and set to initial values on the next zwp_input_method_v2.done\n        event.\n\n        The initial state for affected fields is empty, meaning that the text\n        input does not support sending surrounding text. If the empty values\n        get applied, subsequent attempts to change them may have no effect.\n      </description>\n      <arg name=\"text\" type=\"string\"/>\n      <arg name=\"cursor\" type=\"uint\"/>\n      <arg name=\"anchor\" type=\"uint\"/>\n    </event>\n\n    <event name=\"text_change_cause\">\n      <description summary=\"indicates the cause of surrounding text change\">\n        Tells the input method why the text surrounding the cursor changed.\n\n        Whenever the client detects an external change in text, cursor, or\n        anchor position, it must issue this request to the compositor. This\n        request is intended to give the input method a chance to update the\n        preedit text in an appropriate way, e.g. by removing it when the user\n        starts typing with a keyboard.\n\n        cause describes the source of the change.\n\n        The value set with this event is double-buffered. It will get applied\n        and set to its initial value on the next zwp_input_method_v2.done\n        event.\n\n        The initial value of cause is input_method.\n      </description>\n      <arg name=\"cause\" type=\"uint\" enum=\"zwp_text_input_v3.change_cause\"/>\n    </event>\n\n    <event name=\"content_type\">\n      <description summary=\"content purpose and hint\">\n        Indicates the content type and hint for the current\n        zwp_input_method_v2 instance.\n\n        Values set with this event are double-buffered. They will get applied\n        on the next zwp_input_method_v2.done event.\n\n        The initial value for hint is none, and the initial value for purpose\n        is normal.\n      </description>\n      <arg name=\"hint\" type=\"uint\" enum=\"zwp_text_input_v3.content_hint\"/>\n      <arg name=\"purpose\" type=\"uint\" enum=\"zwp_text_input_v3.content_purpose\"/>\n    </event>\n\n    <event name=\"done\">\n      <description summary=\"apply state\">\n        Atomically applies state changes recently sent to the client.\n\n        The done event establishes and updates the state of the client, and\n        must be issued after any changes to apply them.\n\n        Text input state (content purpose, content hint, surrounding text, and\n        change cause) is conceptually double-buffered within an input method\n        context.\n\n        Events modify the pending state, as opposed to the current state in use\n        by the input method. A done event atomically applies all pending state,\n        replacing the current state. After done, the new pending state is as\n        documented for each related request.\n\n        Events must be applied in the order of arrival.\n\n        Neither current nor pending state are modified unless noted otherwise.\n      </description>\n    </event>\n\n    <request name=\"commit_string\">\n      <description summary=\"commit string\">\n        Send the commit string text for insertion to the application.\n\n        Inserts a string at current cursor position (see commit event\n        sequence). The string to commit could be either just a single character\n        after a key press or the result of some composing.\n\n        The argument text is a buffer containing the string to insert. There is\n        a maximum length of wayland messages, so text can not be longer than\n        4000 bytes.\n\n        Values set with this event are double-buffered. They must be applied\n        and reset to initial on the next zwp_text_input_v3.commit request.\n\n        The initial value of text is an empty string.\n      </description>\n      <arg name=\"text\" type=\"string\"/>\n    </request>\n\n    <request name=\"set_preedit_string\">\n      <description summary=\"pre-edit string\">\n        Send the pre-edit string text to the application text input.\n\n        Place a new composing text (pre-edit) at the current cursor position.\n        Any previously set composing text must be removed. Any previously\n        existing selected text must be removed. The cursor is moved to a new\n        position within the preedit string.\n\n        The argument text is a buffer containing the preedit string. There is\n        a maximum length of wayland messages, so text can not be longer than\n        4000 bytes.\n\n        The arguments cursor_begin and cursor_end are counted in bytes relative\n        to the beginning of the submitted string buffer. Cursor should be\n        hidden by the text input when both are equal to -1.\n\n        cursor_begin indicates the beginning of the cursor. cursor_end\n        indicates the end of the cursor. It may be equal or different than\n        cursor_begin.\n\n        Values set with this event are double-buffered. They must be applied on\n        the next zwp_input_method_v2.commit event.\n\n        The initial value of text is an empty string. The initial value of\n        cursor_begin, and cursor_end are both 0.\n      </description>\n      <arg name=\"text\" type=\"string\"/>\n      <arg name=\"cursor_begin\" type=\"int\"/>\n      <arg name=\"cursor_end\" type=\"int\"/>\n    </request>\n\n    <request name=\"delete_surrounding_text\">\n      <description summary=\"delete text\">\n        Remove the surrounding text.\n\n        before_length and after_length are the number of bytes before and after\n        the current cursor index (excluding the preedit text) to delete.\n\n        If any preedit text is present, it is replaced with the cursor for the\n        purpose of this event. In effect before_length is counted from the\n        beginning of preedit text, and after_length from its end (see commit\n        event sequence).\n\n        Values set with this event are double-buffered. They must be applied\n        and reset to initial on the next zwp_input_method_v2.commit request.\n\n        The initial values of both before_length and after_length are 0.\n      </description>\n      <arg name=\"before_length\" type=\"uint\"/>\n      <arg name=\"after_length\" type=\"uint\"/>\n    </request>\n\n    <request name=\"commit\">\n      <description summary=\"apply state\">\n        Apply state changes from commit_string, set_preedit_string and\n        delete_surrounding_text requests.\n\n        The state relating to these events is double-buffered, and each one\n        modifies the pending state. This request replaces the current state\n        with the pending state.\n\n        The connected text input is expected to proceed by evaluating the\n        changes in the following order:\n\n        1. Replace existing preedit string with the cursor.\n        2. Delete requested surrounding text.\n        3. Insert commit string with the cursor at its end.\n        4. Calculate surrounding text to send.\n        5. Insert new preedit text in cursor position.\n        6. Place cursor inside preedit text.\n\n        The serial number reflects the last state of the zwp_input_method_v2\n        object known to the client. The value of the serial argument must be\n        equal to the number of done events already issued by that object. When\n        the compositor receives a commit request with a serial different than\n        the number of past done events, it must proceed as normal, except it\n        should not change the current state of the zwp_input_method_v2 object.\n      </description>\n      <arg name=\"serial\" type=\"uint\"/>\n    </request>\n\n    <request name=\"get_input_popup_surface\">\n      <description summary=\"create popup surface\">\n        Creates a new zwp_input_popup_surface_v2 object wrapping a given\n        surface.\n\n        The surface gets assigned the \"input_popup\" role. If the surface\n        already has an assigned role, the compositor must issue a protocol\n        error.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwp_input_popup_surface_v2\"/>\n      <arg name=\"surface\" type=\"object\" interface=\"wl_surface\"/>\n    </request>\n\n    <request name=\"grab_keyboard\">\n      <description summary=\"grab hardware keyboard\">\n        Allow an input method to receive hardware keyboard input and process\n        key events to generate text events (with pre-edit) over the wire. This\n        allows input methods which compose multiple key events for inputting\n        text like it is done for CJK languages.\n\n        The compositor should send all keyboard events on the seat to the grab\n        holder via the returned wl_keyboard object. Nevertheless, the\n        compositor may decide not to forward any particular event. The\n        compositor must not further process any event after it has been\n        forwarded to the grab holder.\n\n        Releasing the resulting wl_keyboard object releases the grab.\n      </description>\n      <arg name=\"keyboard\" type=\"new_id\"\n        interface=\"zwp_input_method_keyboard_grab_v2\"/>\n    </request>\n\n    <event name=\"unavailable\">\n      <description summary=\"input method unavailable\">\n        The input method ceased to be available.\n\n        The compositor must issue this event as the only event on the object if\n        there was another input_method object associated with the same seat at\n        the time of its creation.\n\n        The compositor must issue this request when the object is no longer\n        usable, e.g. due to seat removal.\n\n        The input method context becomes inert and should be destroyed after\n        deactivation is handled. Any further requests and events except for the\n        destroy request must be ignored.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the text input\">\n        Destroys the zwp_text_input_v2 object and any associated child\n        objects, i.e. zwp_input_popup_surface_v2 and\n        zwp_input_method_keyboard_grab_v2.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwp_input_popup_surface_v2\" version=\"1\">\n    <description summary=\"popup surface\">\n      This interface marks a surface as a popup for interacting with an input\n      method.\n\n      The compositor should place it near the active text input area. It must\n      be visible if and only if the input method is in the active state.\n\n      The client must not destroy the underlying wl_surface while the\n      zwp_input_popup_surface_v2 object exists.\n    </description>\n\n    <event name=\"text_input_rectangle\">\n      <description summary=\"set text input area position\">\n        Notify about the position of the area of the text input expressed as a\n        rectangle in surface local coordinates.\n\n        This is a hint to the input method telling it the relative position of\n        the text being entered.\n      </description>\n      <arg name=\"x\" type=\"int\"/>\n      <arg name=\"y\" type=\"int\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\"/>\n  </interface>\n\n  <interface name=\"zwp_input_method_keyboard_grab_v2\" version=\"1\">\n    <!-- Closely follows wl_keyboard version 6 -->\n    <description summary=\"keyboard grab\">\n      The zwp_input_method_keyboard_grab_v2 interface represents an exclusive\n      grab of the wl_keyboard interface associated with the seat.\n    </description>\n\n    <event name=\"keymap\">\n      <description summary=\"keyboard mapping\">\n        This event provides a file descriptor to the client which can be\n        memory-mapped to provide a keyboard mapping description.\n      </description>\n      <arg name=\"format\" type=\"uint\" enum=\"wl_keyboard.keymap_format\"\n        summary=\"keymap format\"/>\n      <arg name=\"fd\" type=\"fd\" summary=\"keymap file descriptor\"/>\n      <arg name=\"size\" type=\"uint\" summary=\"keymap size, in bytes\"/>\n    </event>\n\n    <event name=\"key\">\n      <description summary=\"key event\">\n        A key was pressed or released.\n        The time argument is a timestamp with millisecond granularity, with an\n        undefined base.\n      </description>\n      <arg name=\"serial\" type=\"uint\" summary=\"serial number of the key event\"/>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"key\" type=\"uint\" summary=\"key that produced the event\"/>\n      <arg name=\"state\" type=\"uint\" enum=\"wl_keyboard.key_state\"\n        summary=\"physical state of the key\"/>\n    </event>\n\n    <event name=\"modifiers\">\n      <description summary=\"modifier and group state\">\n        Notifies clients that the modifier and/or group state has changed, and\n        it should update its local state.\n      </description>\n      <arg name=\"serial\" type=\"uint\" summary=\"serial number of the modifiers event\"/>\n      <arg name=\"mods_depressed\" type=\"uint\" summary=\"depressed modifiers\"/>\n      <arg name=\"mods_latched\" type=\"uint\" summary=\"latched modifiers\"/>\n      <arg name=\"mods_locked\" type=\"uint\" summary=\"locked modifiers\"/>\n      <arg name=\"group\" type=\"uint\" summary=\"keyboard layout\"/>\n    </event>\n\n    <request name=\"release\" type=\"destructor\">\n      <description summary=\"release the grab object\"/>\n    </request>\n\n    <event name=\"repeat_info\">\n      <description summary=\"repeat rate and delay\">\n        Informs the client about the keyboard's repeat rate and delay.\n\n        This event is sent as soon as the zwp_input_method_keyboard_grab_v2\n        object has been created, and is guaranteed to be received by the\n        client before any key press event.\n\n        Negative values for either rate or delay are illegal. A rate of zero\n        will disable any repeating (regardless of the value of delay).\n\n        This event can be sent later on as well with a new value if necessary,\n        so clients should continue listening for the event past the creation\n        of zwp_input_method_keyboard_grab_v2.\n      </description>\n      <arg name=\"rate\" type=\"int\"\n\t   summary=\"the rate of repeating keys in characters per second\"/>\n      <arg name=\"delay\" type=\"int\"\n\t   summary=\"delay in milliseconds since key down until repeating starts\"/>\n    </event>\n  </interface>\n\n  <interface name=\"zwp_input_method_manager_v2\" version=\"1\">\n    <description summary=\"input method manager\">\n      The input method manager allows the client to become the input method on\n      a chosen seat.\n\n      No more than one input method must be associated with any seat at any\n      given time.\n    </description>\n\n    <request name=\"get_input_method\">\n      <description summary=\"request an input method object\">\n        Request a new input zwp_input_method_v2 object associated with a given\n        seat.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n      <arg name=\"input_method\" type=\"new_id\" interface=\"zwp_input_method_v2\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the input method manager\">\n        Destroys the zwp_input_method_manager_v2 object.\n\n        The zwp_input_method_v2 objects originating from it remain valid.\n      </description>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/kde-server-decoration.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"server_decoration\">\n  <copyright><![CDATA[\n    SPDX-FileCopyrightText: 2015 Martin Gräßlin\n\n    SPDX-License-Identifier: LGPL-2.1-or-later\n  ]]></copyright>\n  <interface  name=\"org_kde_kwin_server_decoration_manager\" version=\"1\">\n      <description summary=\"Server side window decoration manager\">\n        This interface allows to coordinate whether the server should create\n        a server-side window decoration around a wl_surface representing a\n        shell surface (wl_shell_surface or similar). By announcing support\n        for this interface the server indicates that it supports server\n        side decorations.\n\n        Use in conjunction with zxdg_decoration_manager_v1 is undefined.\n      </description>\n      <request name=\"create\">\n        <description summary=\"Create a server-side decoration object for a given surface\">\n            When a client creates a server-side decoration object it indicates\n            that it supports the protocol. The client is supposed to tell the\n            server whether it wants server-side decorations or will provide\n            client-side decorations.\n\n            If the client does not create a server-side decoration object for\n            a surface the server interprets this as lack of support for this\n            protocol and considers it as client-side decorated. Nevertheless a\n            client-side decorated surface should use this protocol to indicate\n            to the server that it does not want a server-side deco.\n        </description>\n        <arg name=\"id\" type=\"new_id\" interface=\"org_kde_kwin_server_decoration\"/>\n        <arg name=\"surface\" type=\"object\" interface=\"wl_surface\"/>\n      </request>\n      <enum name=\"mode\">\n            <description summary=\"Possible values to use in request_mode and the event mode.\"/>\n            <entry name=\"None\" value=\"0\" summary=\"Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated.\"/>\n            <entry name=\"Client\" value=\"1\" summary=\"Client-side decoration: The decoration is part of the surface and the client.\"/>\n            <entry name=\"Server\" value=\"2\" summary=\"Server-side decoration: The server embeds the surface into a decoration frame.\"/>\n      </enum>\n      <event name=\"default_mode\">\n          <description summary=\"The default mode used on the server\">\n              This event is emitted directly after binding the interface. It contains\n              the default mode for the decoration. When a new server decoration object\n              is created this new object will be in the default mode until the first\n              request_mode is requested.\n\n              The server may change the default mode at any time.\n          </description>\n          <arg name=\"mode\" type=\"uint\" summary=\"The default decoration mode applied to newly created server decorations.\"/>\n      </event>\n  </interface>\n  <interface name=\"org_kde_kwin_server_decoration\" version=\"1\">\n      <request name=\"release\" type=\"destructor\">\n        <description summary=\"release the server decoration object\"/>\n      </request>\n      <enum name=\"mode\">\n            <description summary=\"Possible values to use in request_mode and the event mode.\"/>\n            <entry name=\"None\" value=\"0\" summary=\"Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated.\"/>\n            <entry name=\"Client\" value=\"1\" summary=\"Client-side decoration: The decoration is part of the surface and the client.\"/>\n            <entry name=\"Server\" value=\"2\" summary=\"Server-side decoration: The server embeds the surface into a decoration frame.\"/>\n      </enum>\n      <request name=\"request_mode\">\n          <description summary=\"The decoration mode the surface wants to use.\"/>\n          <arg name=\"mode\" type=\"uint\" summary=\"The mode this surface wants to use.\"/>\n      </request>\n      <event name=\"mode\">\n          <description summary=\"The new decoration mode applied by the server\">\n              This event is emitted directly after the decoration is created and\n              represents the base decoration policy by the server. E.g. a server\n              which wants all surfaces to be client-side decorated will send Client,\n              a server which wants server-side decoration will send Server.\n\n              The client can request a different mode through the decoration request.\n              The server will acknowledge this by another event with the same mode. So\n              even if a server prefers server-side decoration it's possible to force a\n              client-side decoration.\n\n              The server may emit this event at any time. In this case the client can\n              again request a different mode. It's the responsibility of the server to\n              prevent a feedback loop.\n          </description>\n          <arg name=\"mode\" type=\"uint\" summary=\"The decoration mode applied to the surface by the server.\"/>\n      </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/virtual-keyboard-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"virtual_keyboard_unstable_v1\">\n  <copyright>\n    Copyright © 2008-2011  Kristian Høgsberg\n    Copyright © 2010-2013  Intel Corporation\n    Copyright © 2012-2013  Collabora, Ltd.\n    Copyright © 2018       Purism SPC\n\n    Permission is hereby granted, free of charge, to any person obtaining a\n    copy of this software and associated documentation files (the \"Software\"),\n    to deal in the Software without restriction, including without limitation\n    the rights to use, copy, modify, merge, publish, distribute, sublicense,\n    and/or sell copies of the Software, and to permit persons to whom the\n    Software is furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice (including the next\n    paragraph) shall be included in all copies or substantial portions of the\n    Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n    DEALINGS IN THE SOFTWARE.\n  </copyright>\n\n  <interface name=\"zwp_virtual_keyboard_v1\" version=\"1\">\n    <description summary=\"virtual keyboard\">\n      The virtual keyboard provides an application with requests which emulate\n      the behaviour of a physical keyboard.\n\n      This interface can be used by clients on its own to provide raw input\n      events, or it can accompany the input method protocol.\n    </description>\n\n    <request name=\"keymap\">\n      <description summary=\"keyboard mapping\">\n        Provide a file descriptor to the compositor which can be\n        memory-mapped to provide a keyboard mapping description.\n\n        Format carries a value from the keymap_format enumeration.\n      </description>\n      <arg name=\"format\" type=\"uint\" summary=\"keymap format\"/>\n      <arg name=\"fd\" type=\"fd\" summary=\"keymap file descriptor\"/>\n      <arg name=\"size\" type=\"uint\" summary=\"keymap size, in bytes\"/>\n    </request>\n\n    <enum name=\"error\">\n      <entry name=\"no_keymap\" value=\"0\" summary=\"No keymap was set\"/>\n    </enum>\n\n    <request name=\"key\">\n      <description summary=\"key event\">\n        A key was pressed or released.\n        The time argument is a timestamp with millisecond granularity, with an\n        undefined base. All requests regarding a single object must share the\n        same clock.\n\n        Keymap must be set before issuing this request.\n\n        State carries a value from the key_state enumeration.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"key\" type=\"uint\" summary=\"key that produced the event\"/>\n      <arg name=\"state\" type=\"uint\" summary=\"physical state of the key\"/>\n    </request>\n\n    <request name=\"modifiers\">\n      <description summary=\"modifier and group state\">\n        Notifies the compositor that the modifier and/or group state has\n        changed, and it should update state.\n\n        The client should use wl_keyboard.modifiers event to synchronize its\n        internal state with seat state.\n\n        Keymap must be set before issuing this request.\n      </description>\n      <arg name=\"mods_depressed\" type=\"uint\" summary=\"depressed modifiers\"/>\n      <arg name=\"mods_latched\" type=\"uint\" summary=\"latched modifiers\"/>\n      <arg name=\"mods_locked\" type=\"uint\" summary=\"locked modifiers\"/>\n      <arg name=\"group\" type=\"uint\" summary=\"keyboard layout\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\" since=\"1\">\n      <description summary=\"destroy the virtual keyboard keyboard object\"/>\n    </request>\n  </interface>\n\n  <interface name=\"zwp_virtual_keyboard_manager_v1\" version=\"1\">\n    <description summary=\"virtual keyboard manager\">\n      A virtual keyboard manager allows an application to provide keyboard\n      input events as if they came from a physical keyboard.\n    </description>\n\n    <enum name=\"error\">\n      <entry name=\"unauthorized\" value=\"0\" summary=\"client not authorized to use the interface\"/>\n    </enum>\n\n    <request name=\"create_virtual_keyboard\">\n      <description summary=\"Create a new virtual keyboard\">\n        Creates a new virtual keyboard associated to a seat.\n\n        If the compositor enables a keyboard to perform arbitrary actions, it\n        should present an error when an untrusted client requests a new\n        keyboard.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwp_virtual_keyboard_v1\"/>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wayland-drm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"drm\">\n\n  <copyright>\n    Copyright © 2008-2011 Kristian Høgsberg\n    Copyright © 2010-2011 Intel Corporation\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that\\n the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <!-- drm support. This object is created by the server and published\n       using the display's global event. -->\n  <interface name=\"wl_drm\" version=\"2\">\n    <enum name=\"error\">\n      <entry name=\"authenticate_fail\" value=\"0\"/>\n      <entry name=\"invalid_format\" value=\"1\"/>\n      <entry name=\"invalid_name\" value=\"2\"/>\n    </enum>\n\n    <enum name=\"format\">\n      <!-- The drm format codes match the #defines in drm_fourcc.h.\n           The formats actually supported by the compositor will be\n           reported by the format event. New codes must not be added,\n           unless directly taken from drm_fourcc.h. -->\n      <entry name=\"c8\" value=\"0x20203843\"/>\n      <entry name=\"rgb332\" value=\"0x38424752\"/>\n      <entry name=\"bgr233\" value=\"0x38524742\"/>\n      <entry name=\"xrgb4444\" value=\"0x32315258\"/>\n      <entry name=\"xbgr4444\" value=\"0x32314258\"/>\n      <entry name=\"rgbx4444\" value=\"0x32315852\"/>\n      <entry name=\"bgrx4444\" value=\"0x32315842\"/>\n      <entry name=\"argb4444\" value=\"0x32315241\"/>\n      <entry name=\"abgr4444\" value=\"0x32314241\"/>\n      <entry name=\"rgba4444\" value=\"0x32314152\"/>\n      <entry name=\"bgra4444\" value=\"0x32314142\"/>\n      <entry name=\"xrgb1555\" value=\"0x35315258\"/>\n      <entry name=\"xbgr1555\" value=\"0x35314258\"/>\n      <entry name=\"rgbx5551\" value=\"0x35315852\"/>\n      <entry name=\"bgrx5551\" value=\"0x35315842\"/>\n      <entry name=\"argb1555\" value=\"0x35315241\"/>\n      <entry name=\"abgr1555\" value=\"0x35314241\"/>\n      <entry name=\"rgba5551\" value=\"0x35314152\"/>\n      <entry name=\"bgra5551\" value=\"0x35314142\"/>\n      <entry name=\"rgb565\" value=\"0x36314752\"/>\n      <entry name=\"bgr565\" value=\"0x36314742\"/>\n      <entry name=\"rgb888\" value=\"0x34324752\"/>\n      <entry name=\"bgr888\" value=\"0x34324742\"/>\n      <entry name=\"xrgb8888\" value=\"0x34325258\"/>\n      <entry name=\"xbgr8888\" value=\"0x34324258\"/>\n      <entry name=\"rgbx8888\" value=\"0x34325852\"/>\n      <entry name=\"bgrx8888\" value=\"0x34325842\"/>\n      <entry name=\"argb8888\" value=\"0x34325241\"/>\n      <entry name=\"abgr8888\" value=\"0x34324241\"/>\n      <entry name=\"rgba8888\" value=\"0x34324152\"/>\n      <entry name=\"bgra8888\" value=\"0x34324142\"/>\n      <entry name=\"xrgb2101010\" value=\"0x30335258\"/>\n      <entry name=\"xbgr2101010\" value=\"0x30334258\"/>\n      <entry name=\"rgbx1010102\" value=\"0x30335852\"/>\n      <entry name=\"bgrx1010102\" value=\"0x30335842\"/>\n      <entry name=\"argb2101010\" value=\"0x30335241\"/>\n      <entry name=\"abgr2101010\" value=\"0x30334241\"/>\n      <entry name=\"rgba1010102\" value=\"0x30334152\"/>\n      <entry name=\"bgra1010102\" value=\"0x30334142\"/>\n      <entry name=\"yuyv\" value=\"0x56595559\"/>\n      <entry name=\"yvyu\" value=\"0x55595659\"/>\n      <entry name=\"uyvy\" value=\"0x59565955\"/>\n      <entry name=\"vyuy\" value=\"0x59555956\"/>\n      <entry name=\"ayuv\" value=\"0x56555941\"/>\n      <entry name=\"xyuv8888\" value=\"0x56555958\"/>\n      <entry name=\"nv12\" value=\"0x3231564e\"/>\n      <entry name=\"nv21\" value=\"0x3132564e\"/>\n      <entry name=\"nv16\" value=\"0x3631564e\"/>\n      <entry name=\"nv61\" value=\"0x3136564e\"/>\n      <entry name=\"yuv410\" value=\"0x39565559\"/>\n      <entry name=\"yvu410\" value=\"0x39555659\"/>\n      <entry name=\"yuv411\" value=\"0x31315559\"/>\n      <entry name=\"yvu411\" value=\"0x31315659\"/>\n      <entry name=\"yuv420\" value=\"0x32315559\"/>\n      <entry name=\"yvu420\" value=\"0x32315659\"/>\n      <entry name=\"yuv422\" value=\"0x36315559\"/>\n      <entry name=\"yvu422\" value=\"0x36315659\"/>\n      <entry name=\"yuv444\" value=\"0x34325559\"/>\n      <entry name=\"yvu444\" value=\"0x34325659\"/>\n      <entry name=\"abgr16f\" value=\"0x48344241\"/>\n      <entry name=\"xbgr16f\" value=\"0x48344258\"/>\n    </enum>\n\n    <!-- Call this request with the magic received from drmGetMagic().\n         It will be passed on to the drmAuthMagic() or\n         DRIAuthConnection() call.  This authentication must be\n         completed before create_buffer could be used. -->\n    <request name=\"authenticate\">\n      <arg name=\"id\" type=\"uint\"/>\n    </request>\n\n    <!-- Create a wayland buffer for the named DRM buffer.  The DRM\n         surface must have a name using the flink ioctl -->\n    <request name=\"create_buffer\">\n      <arg name=\"id\" type=\"new_id\" interface=\"wl_buffer\"/>\n      <arg name=\"name\" type=\"uint\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n      <arg name=\"stride\" type=\"uint\"/>\n      <arg name=\"format\" type=\"uint\"/>\n    </request>\n\n    <!-- Create a wayland buffer for the named DRM buffer.  The DRM\n         surface must have a name using the flink ioctl -->\n    <request name=\"create_planar_buffer\">\n      <arg name=\"id\" type=\"new_id\" interface=\"wl_buffer\"/>\n      <arg name=\"name\" type=\"uint\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n      <arg name=\"format\" type=\"uint\"/>\n      <arg name=\"offset0\" type=\"int\"/>\n      <arg name=\"stride0\" type=\"int\"/>\n      <arg name=\"offset1\" type=\"int\"/>\n      <arg name=\"stride1\" type=\"int\"/>\n      <arg name=\"offset2\" type=\"int\"/>\n      <arg name=\"stride2\" type=\"int\"/>\n    </request>\n\n    <!-- Notification of the path of the drm device which is used by\n         the server.  The client should use this device for creating\n         local buffers.  Only buffers created from this device should\n         be be passed to the server using this drm object's\n         create_buffer request. -->\n    <event name=\"device\">\n      <arg name=\"name\" type=\"string\"/>\n    </event>\n\n    <event name=\"format\">\n      <arg name=\"format\" type=\"uint\"/>\n    </event>\n\n    <!-- Raised if the authenticate request succeeded -->\n    <event name=\"authenticated\"/>\n\n    <enum name=\"capability\" since=\"2\">\n      <description summary=\"wl_drm capability bitmask\">\n        Bitmask of capabilities.\n      </description>\n      <entry name=\"prime\" value=\"1\" summary=\"wl_drm prime available\"/>\n    </enum>\n\n    <event name=\"capabilities\">\n      <arg name=\"value\" type=\"uint\"/>\n    </event>\n\n    <!-- Version 2 additions -->\n\n    <!-- Create a wayland buffer for the prime fd.  Use for regular and planar\n         buffers.  Pass 0 for offset and stride for unused planes. -->\n    <request name=\"create_prime_buffer\" since=\"2\">\n      <arg name=\"id\" type=\"new_id\" interface=\"wl_buffer\"/>\n      <arg name=\"name\" type=\"fd\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n      <arg name=\"format\" type=\"uint\"/>\n      <arg name=\"offset0\" type=\"int\"/>\n      <arg name=\"stride0\" type=\"int\"/>\n      <arg name=\"offset1\" type=\"int\"/>\n      <arg name=\"stride1\" type=\"int\"/>\n      <arg name=\"offset2\" type=\"int\"/>\n      <arg name=\"stride2\" type=\"int\"/>\n    </request>\n\n  </interface>\n\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-data-control-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_data_control_unstable_v1\">\n  <copyright>\n    Copyright © 2018 Simon Ser\n    Copyright © 2019 Ivan Molodetskikh\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <description summary=\"control data devices\">\n    This protocol allows a privileged client to control data devices. In\n    particular, the client will be able to manage the current selection and take\n    the role of a clipboard manager.\n\n    Warning! The protocol described in this file is experimental and\n    backward incompatible changes may be made. Backward compatible changes\n    may be added together with the corresponding interface version bump.\n    Backward incompatible changes are done by bumping the version number in\n    the protocol and interface names and resetting the interface version.\n    Once the protocol is to be declared stable, the 'z' prefix and the\n    version number in the protocol and interface names are removed and the\n    interface version number is reset.\n  </description>\n\n  <interface name=\"zwlr_data_control_manager_v1\" version=\"2\">\n    <description summary=\"manager to control data devices\">\n      This interface is a manager that allows creating per-seat data device\n      controls.\n    </description>\n\n    <request name=\"create_data_source\">\n      <description summary=\"create a new data source\">\n        Create a new data source.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_data_control_source_v1\"\n        summary=\"data source to create\"/>\n    </request>\n\n    <request name=\"get_data_device\">\n      <description summary=\"get a data device for a seat\">\n        Create a data device that can be used to manage a seat's selection.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_data_control_device_v1\"/>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the manager\">\n        All objects created by the manager will still remain valid, until their\n        appropriate destroy request has been called.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_data_control_device_v1\" version=\"2\">\n    <description summary=\"manage a data device for a seat\">\n      This interface allows a client to manage a seat's selection.\n\n      When the seat is destroyed, this object becomes inert.\n    </description>\n\n    <request name=\"set_selection\">\n      <description summary=\"copy data to the selection\">\n        This request asks the compositor to set the selection to the data from\n        the source on behalf of the client.\n\n        The given source may not be used in any further set_selection or\n        set_primary_selection requests. Attempting to use a previously used\n        source is a protocol error.\n\n        To unset the selection, set the source to NULL.\n      </description>\n      <arg name=\"source\" type=\"object\" interface=\"zwlr_data_control_source_v1\"\n        allow-null=\"true\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy this data device\">\n        Destroys the data device object.\n      </description>\n    </request>\n\n    <event name=\"data_offer\">\n      <description summary=\"introduce a new wlr_data_control_offer\">\n        The data_offer event introduces a new wlr_data_control_offer object,\n        which will subsequently be used in either the\n        wlr_data_control_device.selection event (for the regular clipboard\n        selections) or the wlr_data_control_device.primary_selection event (for\n        the primary clipboard selections). Immediately following the\n        wlr_data_control_device.data_offer event, the new data_offer object\n        will send out wlr_data_control_offer.offer events to describe the MIME\n        types it offers.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_data_control_offer_v1\"/>\n    </event>\n\n    <event name=\"selection\">\n      <description summary=\"advertise new selection\">\n        The selection event is sent out to notify the client of a new\n        wlr_data_control_offer for the selection for this device. The\n        wlr_data_control_device.data_offer and the wlr_data_control_offer.offer\n        events are sent out immediately before this event to introduce the data\n        offer object. The selection event is sent to a client when a new\n        selection is set. The wlr_data_control_offer is valid until a new\n        wlr_data_control_offer or NULL is received. The client must destroy the\n        previous selection wlr_data_control_offer, if any, upon receiving this\n        event.\n\n        The first selection event is sent upon binding the\n        wlr_data_control_device object.\n      </description>\n      <arg name=\"id\" type=\"object\" interface=\"zwlr_data_control_offer_v1\"\n        allow-null=\"true\"/>\n    </event>\n\n    <event name=\"finished\">\n      <description summary=\"this data control is no longer valid\">\n        This data control object is no longer valid and should be destroyed by\n        the client.\n      </description>\n    </event>\n\n    <!-- Version 2 additions -->\n\n    <event name=\"primary_selection\" since=\"2\">\n      <description summary=\"advertise new primary selection\">\n        The primary_selection event is sent out to notify the client of a new\n        wlr_data_control_offer for the primary selection for this device. The\n        wlr_data_control_device.data_offer and the wlr_data_control_offer.offer\n        events are sent out immediately before this event to introduce the data\n        offer object. The primary_selection event is sent to a client when a\n        new primary selection is set. The wlr_data_control_offer is valid until\n        a new wlr_data_control_offer or NULL is received. The client must\n        destroy the previous primary selection wlr_data_control_offer, if any,\n        upon receiving this event.\n\n        If the compositor supports primary selection, the first\n        primary_selection event is sent upon binding the\n        wlr_data_control_device object.\n      </description>\n      <arg name=\"id\" type=\"object\" interface=\"zwlr_data_control_offer_v1\"\n        allow-null=\"true\"/>\n    </event>\n\n    <request name=\"set_primary_selection\" since=\"2\">\n      <description summary=\"copy data to the primary selection\">\n        This request asks the compositor to set the primary selection to the\n        data from the source on behalf of the client.\n\n        The given source may not be used in any further set_selection or\n        set_primary_selection requests. Attempting to use a previously used\n        source is a protocol error.\n\n        To unset the primary selection, set the source to NULL.\n\n        The compositor will ignore this request if it does not support primary\n        selection.\n      </description>\n      <arg name=\"source\" type=\"object\" interface=\"zwlr_data_control_source_v1\"\n        allow-null=\"true\"/>\n    </request>\n\n    <enum name=\"error\" since=\"2\">\n      <entry name=\"used_source\" value=\"1\"\n        summary=\"source given to set_selection or set_primary_selection was already used before\"/>\n    </enum>\n  </interface>\n\n  <interface name=\"zwlr_data_control_source_v1\" version=\"1\">\n    <description summary=\"offer to transfer data\">\n      The wlr_data_control_source object is the source side of a\n      wlr_data_control_offer. It is created by the source client in a data\n      transfer and provides a way to describe the offered data and a way to\n      respond to requests to transfer the data.\n    </description>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_offer\" value=\"1\"\n        summary=\"offer sent after wlr_data_control_device.set_selection\"/>\n    </enum>\n\n    <request name=\"offer\">\n      <description summary=\"add an offered MIME type\">\n        This request adds a MIME type to the set of MIME types advertised to\n        targets. Can be called several times to offer multiple types.\n\n        Calling this after wlr_data_control_device.set_selection is a protocol\n        error.\n      </description>\n      <arg name=\"mime_type\" type=\"string\"\n        summary=\"MIME type offered by the data source\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy this source\">\n        Destroys the data source object.\n      </description>\n    </request>\n\n    <event name=\"send\">\n      <description summary=\"send the data\">\n        Request for data from the client. Send the data as the specified MIME\n        type over the passed file descriptor, then close it.\n      </description>\n      <arg name=\"mime_type\" type=\"string\" summary=\"MIME type for the data\"/>\n      <arg name=\"fd\" type=\"fd\" summary=\"file descriptor for the data\"/>\n    </event>\n\n    <event name=\"cancelled\">\n      <description summary=\"selection was cancelled\">\n        This data source is no longer valid. The data source has been replaced\n        by another data source.\n\n        The client should clean up and destroy this data source.\n      </description>\n    </event>\n  </interface>\n\n  <interface name=\"zwlr_data_control_offer_v1\" version=\"1\">\n    <description summary=\"offer to transfer data\">\n      A wlr_data_control_offer represents a piece of data offered for transfer\n      by another client (the source client). The offer describes the different\n      MIME types that the data can be converted to and provides the mechanism\n      for transferring the data directly from the source client.\n    </description>\n\n    <request name=\"receive\">\n      <description summary=\"request that the data is transferred\">\n        To transfer the offered data, the client issues this request and\n        indicates the MIME type it wants to receive. The transfer happens\n        through the passed file descriptor (typically created with the pipe\n        system call). The source client writes the data in the MIME type\n        representation requested and then closes the file descriptor.\n\n        The receiving client reads from the read end of the pipe until EOF and\n        then closes its end, at which point the transfer is complete.\n\n        This request may happen multiple times for different MIME types.\n      </description>\n      <arg name=\"mime_type\" type=\"string\"\n        summary=\"MIME type desired by receiver\"/>\n      <arg name=\"fd\" type=\"fd\" summary=\"file descriptor for data transfer\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy this offer\">\n        Destroys the data offer object.\n      </description>\n    </request>\n\n    <event name=\"offer\">\n      <description summary=\"advertise offered MIME type\">\n        Sent immediately after creating the wlr_data_control_offer object.\n        One event per offered MIME type.\n      </description>\n      <arg name=\"mime_type\" type=\"string\" summary=\"offered MIME type\"/>\n    </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-foreign-toplevel-management-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_foreign_toplevel_management_unstable_v1\">\n  <copyright>\n    Copyright © 2018 Ilia Bozhinov\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <interface name=\"zwlr_foreign_toplevel_manager_v1\" version=\"3\">\n    <description summary=\"list and control opened apps\">\n      The purpose of this protocol is to enable the creation of taskbars\n      and docks by providing them with a list of opened applications and\n      letting them request certain actions on them, like maximizing, etc.\n\n      After a client binds the zwlr_foreign_toplevel_manager_v1, each opened\n      toplevel window will be sent via the toplevel event\n    </description>\n\n    <event name=\"toplevel\">\n      <description summary=\"a toplevel has been created\">\n        This event is emitted whenever a new toplevel window is created. It\n        is emitted for all toplevels, regardless of the app that has created\n        them.\n\n        All initial details of the toplevel(title, app_id, states, etc.) will\n        be sent immediately after this event via the corresponding events in\n        zwlr_foreign_toplevel_handle_v1.\n      </description>\n      <arg name=\"toplevel\" type=\"new_id\" interface=\"zwlr_foreign_toplevel_handle_v1\"/>\n    </event>\n\n    <request name=\"stop\">\n      <description summary=\"stop sending events\">\n        Indicates the client no longer wishes to receive events for new toplevels.\n        However the compositor may emit further toplevel_created events, until\n        the finished event is emitted.\n\n        The client must not send any more requests after this one.\n      </description>\n    </request>\n\n    <event name=\"finished\" type=\"destructor\">\n      <description summary=\"the compositor has finished with the toplevel manager\">\n        This event indicates that the compositor is done sending events to the\n        zwlr_foreign_toplevel_manager_v1. The server will destroy the object\n        immediately after sending this request, so it will become invalid and\n        the client should free any resources associated with it.\n      </description>\n    </event>\n  </interface>\n\n  <interface name=\"zwlr_foreign_toplevel_handle_v1\" version=\"3\">\n    <description summary=\"an opened toplevel\">\n      A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel\n      window. Each app may have multiple opened toplevels.\n\n      Each toplevel has a list of outputs it is visible on, conveyed to the\n      client with the output_enter and output_leave events.\n    </description>\n\n    <event name=\"title\">\n      <description summary=\"title change\">\n        This event is emitted whenever the title of the toplevel changes.\n      </description>\n      <arg name=\"title\" type=\"string\"/>\n    </event>\n\n    <event name=\"app_id\">\n      <description summary=\"app-id change\">\n        This event is emitted whenever the app-id of the toplevel changes.\n      </description>\n      <arg name=\"app_id\" type=\"string\"/>\n    </event>\n\n    <event name=\"output_enter\">\n      <description summary=\"toplevel entered an output\">\n        This event is emitted whenever the toplevel becomes visible on\n        the given output. A toplevel may be visible on multiple outputs.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </event>\n\n    <event name=\"output_leave\">\n      <description summary=\"toplevel left an output\">\n        This event is emitted whenever the toplevel stops being visible on\n        the given output. It is guaranteed that an entered-output event\n        with the same output has been emitted before this event.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </event>\n\n    <request name=\"set_maximized\">\n      <description summary=\"requests that the toplevel be maximized\">\n        Requests that the toplevel be maximized. If the maximized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"unset_maximized\">\n      <description summary=\"requests that the toplevel be unmaximized\">\n        Requests that the toplevel be unmaximized. If the maximized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"set_minimized\">\n      <description summary=\"requests that the toplevel be minimized\">\n        Requests that the toplevel be minimized. If the minimized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"unset_minimized\">\n      <description summary=\"requests that the toplevel be unminimized\">\n        Requests that the toplevel be unminimized. If the minimized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"activate\">\n      <description summary=\"activate the toplevel\">\n        Request that this toplevel be activated on the given seat.\n        There is no guarantee the toplevel will be actually activated.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n    </request>\n\n    <enum name=\"state\">\n      <description summary=\"types of states on the toplevel\">\n        The different states that a toplevel can have. These have the same meaning\n        as the states with the same names defined in xdg-toplevel\n      </description>\n\n      <entry name=\"maximized\"  value=\"0\" summary=\"the toplevel is maximized\"/>\n      <entry name=\"minimized\"  value=\"1\" summary=\"the toplevel is minimized\"/>\n      <entry name=\"activated\"  value=\"2\" summary=\"the toplevel is active\"/>\n      <entry name=\"fullscreen\" value=\"3\" summary=\"the toplevel is fullscreen\" since=\"2\"/>\n    </enum>\n\n    <event name=\"state\">\n      <description summary=\"the toplevel state changed\">\n        This event is emitted immediately after the zlw_foreign_toplevel_handle_v1\n        is created and each time the toplevel state changes, either because of a\n        compositor action or because of a request in this protocol.\n      </description>\n\n      <arg name=\"state\" type=\"array\"/>\n    </event>\n\n    <event name=\"done\">\n      <description summary=\"all information about the toplevel has been sent\">\n        This event is sent after all changes in the toplevel state have been\n        sent.\n\n        This allows changes to the zwlr_foreign_toplevel_handle_v1 properties\n        to be seen as atomic, even if they happen via multiple events.\n      </description>\n    </event>\n\n    <request name=\"close\">\n      <description summary=\"request that the toplevel be closed\">\n        Send a request to the toplevel to close itself. The compositor would\n        typically use a shell-specific method to carry out this request, for\n        example by sending the xdg_toplevel.close event. However, this gives\n        no guarantees the toplevel will actually be destroyed. If and when\n        this happens, the zwlr_foreign_toplevel_handle_v1.closed event will\n        be emitted.\n      </description>\n    </request>\n\n    <request name=\"set_rectangle\">\n      <description summary=\"the rectangle which represents the toplevel\">\n        The rectangle of the surface specified in this request corresponds to\n        the place where the app using this protocol represents the given toplevel.\n        It can be used by the compositor as a hint for some operations, e.g\n        minimizing. The client is however not required to set this, in which\n        case the compositor is free to decide some default value.\n\n        If the client specifies more than one rectangle, only the last one is\n        considered.\n\n        The dimensions are given in surface-local coordinates.\n        Setting width=height=0 removes the already-set rectangle.\n      </description>\n\n      <arg name=\"surface\" type=\"object\" interface=\"wl_surface\"/>\n      <arg name=\"x\" type=\"int\"/>\n      <arg name=\"y\" type=\"int\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n    </request>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_rectangle\" value=\"0\"\n        summary=\"the provided rectangle is invalid\"/>\n    </enum>\n\n    <event name=\"closed\">\n      <description summary=\"this toplevel has been destroyed\">\n        This event means the toplevel has been destroyed. It is guaranteed there\n        won't be any more events for this zwlr_foreign_toplevel_handle_v1. The\n        toplevel itself becomes inert so any requests will be ignored except the\n        destroy request.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the zwlr_foreign_toplevel_handle_v1 object\">\n        Destroys the zwlr_foreign_toplevel_handle_v1 object.\n\n        This request should be called either when the client does not want to\n        use the toplevel anymore or after the closed event to finalize the\n        destruction of the object.\n      </description>\n    </request>\n\n    <!-- Version 2 additions -->\n\n    <request name=\"set_fullscreen\" since=\"2\">\n      <description summary=\"request that the toplevel be fullscreened\">\n        Requests that the toplevel be fullscreened on the given output. If the\n        fullscreen state and/or the outputs the toplevel is visible on actually\n        change, this will be indicated by the state and output_enter/leave\n        events.\n\n        The output parameter is only a hint to the compositor. Also, if output\n        is NULL, the compositor should decide which output the toplevel will be\n        fullscreened on, if at all.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\" allow-null=\"true\"/>\n    </request>\n\n    <request name=\"unset_fullscreen\" since=\"2\">\n      <description summary=\"request that the toplevel be unfullscreened\">\n        Requests that the toplevel be unfullscreened. If the fullscreen state\n        actually changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <!-- Version 3 additions -->\n\n    <event name=\"parent\" since=\"3\">\n      <description summary=\"parent change\">\n        This event is emitted whenever the parent of the toplevel changes.\n\n        No event is emitted when the parent handle is destroyed by the client.\n      </description>\n      <arg name=\"parent\" type=\"object\" interface=\"zwlr_foreign_toplevel_handle_v1\" allow-null=\"true\"/>\n    </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-gamma-control-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_gamma_control_unstable_v1\">\n  <copyright>\n    Copyright © 2015 Giulio camuffo\n    Copyright © 2018 Simon Ser\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <description summary=\"manage gamma tables of outputs\">\n    This protocol allows a privileged client to set the gamma tables for\n    outputs.\n\n    Warning! The protocol described in this file is experimental and\n    backward incompatible changes may be made. Backward compatible changes\n    may be added together with the corresponding interface version bump.\n    Backward incompatible changes are done by bumping the version number in\n    the protocol and interface names and resetting the interface version.\n    Once the protocol is to be declared stable, the 'z' prefix and the\n    version number in the protocol and interface names are removed and the\n    interface version number is reset.\n  </description>\n\n  <interface name=\"zwlr_gamma_control_manager_v1\" version=\"1\">\n    <description summary=\"manager to create per-output gamma controls\">\n      This interface is a manager that allows creating per-output gamma\n      controls.\n    </description>\n\n    <request name=\"get_gamma_control\">\n      <description summary=\"get a gamma control for an output\">\n        Create a gamma control that can be used to adjust gamma tables for the\n        provided output.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_gamma_control_v1\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the manager\">\n        All objects created by the manager will still remain valid, until their\n        appropriate destroy request has been called.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_gamma_control_v1\" version=\"1\">\n    <description summary=\"adjust gamma tables for an output\">\n      This interface allows a client to adjust gamma tables for a particular\n      output.\n\n      The client will receive the gamma size, and will then be able to set gamma\n      tables. At any time the compositor can send a failed event indicating that\n      this object is no longer valid.\n\n      There can only be at most one gamma control object per output, which\n      has exclusive access to this particular output. When the gamma control\n      object is destroyed, the gamma table is restored to its original value.\n    </description>\n\n    <event name=\"gamma_size\">\n      <description summary=\"size of gamma ramps\">\n        Advertise the size of each gamma ramp.\n\n        This event is sent immediately when the gamma control object is created.\n      </description>\n      <arg name=\"size\" type=\"uint\" summary=\"number of elements in a ramp\"/>\n    </event>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_gamma\" value=\"1\" summary=\"invalid gamma tables\"/>\n    </enum>\n\n    <request name=\"set_gamma\">\n      <description summary=\"set the gamma table\">\n        Set the gamma table. The file descriptor can be memory-mapped to provide\n        the raw gamma table, which contains successive gamma ramps for the red,\n        green and blue channels. Each gamma ramp is an array of 16-byte unsigned\n        integers which has the same length as the gamma size.\n\n        The file descriptor data must have the same length as three times the\n        gamma size.\n      </description>\n      <arg name=\"fd\" type=\"fd\" summary=\"gamma table file descriptor\"/>\n    </request>\n\n    <event name=\"failed\">\n      <description summary=\"object no longer valid\">\n        This event indicates that the gamma control is no longer valid. This\n        can happen for a number of reasons, including:\n        - The output doesn't support gamma tables\n        - Setting the gamma tables failed\n        - Another client already has exclusive gamma control for this output\n        - The compositor has transferred gamma control to another client\n\n        Upon receiving this event, the client should destroy this object.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy this control\">\n        Destroys the gamma control object. If the object is still valid, this\n        restores the original gamma tables.\n      </description>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-layer-shell-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_layer_shell_unstable_v1\">\n  <copyright>\n    Copyright © 2017 Drew DeVault\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <interface name=\"zwlr_layer_shell_v1\" version=\"5\">\n    <description summary=\"create surfaces that are layers of the desktop\">\n      Clients can use this interface to assign the surface_layer role to\n      wl_surfaces. Such surfaces are assigned to a \"layer\" of the output and\n      rendered with a defined z-depth respective to each other. They may also be\n      anchored to the edges and corners of a screen and specify input handling\n      semantics. This interface should be suitable for the implementation of\n      many desktop shell components, and a broad number of other applications\n      that interact with the desktop.\n    </description>\n\n    <request name=\"get_layer_surface\">\n      <description summary=\"create a layer_surface from a surface\">\n        Create a layer surface for an existing surface. This assigns the role of\n        layer_surface, or raises a protocol error if another role is already\n        assigned.\n\n        Creating a layer surface from a wl_surface which has a buffer attached\n        or committed is a client error, and any attempts by a client to attach\n        or manipulate a buffer prior to the first layer_surface.configure call\n        must also be treated as errors.\n\n        After creating a layer_surface object and setting it up, the client\n        must perform an initial commit without any buffer attached.\n        The compositor will reply with a layer_surface.configure event.\n        The client must acknowledge it and is then allowed to attach a buffer\n        to map the surface.\n\n        You may pass NULL for output to allow the compositor to decide which\n        output to use. Generally this will be the one that the user most\n        recently interacted with.\n\n        Clients can specify a namespace that defines the purpose of the layer\n        surface.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_layer_surface_v1\"/>\n      <arg name=\"surface\" type=\"object\" interface=\"wl_surface\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\" allow-null=\"true\"/>\n      <arg name=\"layer\" type=\"uint\" enum=\"layer\" summary=\"layer to add this surface to\"/>\n      <arg name=\"namespace\" type=\"string\" summary=\"namespace for the layer surface\"/>\n    </request>\n\n    <enum name=\"error\">\n      <entry name=\"role\" value=\"0\" summary=\"wl_surface has another role\"/>\n      <entry name=\"invalid_layer\" value=\"1\" summary=\"layer value is invalid\"/>\n      <entry name=\"already_constructed\" value=\"2\" summary=\"wl_surface has a buffer attached or committed\"/>\n    </enum>\n\n    <enum name=\"layer\">\n      <description summary=\"available layers for surfaces\">\n        These values indicate which layers a surface can be rendered in. They\n        are ordered by z depth, bottom-most first. Traditional shell surfaces\n        will typically be rendered between the bottom and top layers.\n        Fullscreen shell surfaces are typically rendered at the top layer.\n        Multiple surfaces can share a single layer, and ordering within a\n        single layer is undefined.\n      </description>\n\n      <entry name=\"background\" value=\"0\"/>\n      <entry name=\"bottom\" value=\"1\"/>\n      <entry name=\"top\" value=\"2\"/>\n      <entry name=\"overlay\" value=\"3\"/>\n    </enum>\n\n    <!-- Version 3 additions -->\n\n    <request name=\"destroy\" type=\"destructor\" since=\"3\">\n      <description summary=\"destroy the layer_shell object\">\n        This request indicates that the client will not use the layer_shell\n        object any more. Objects that have been created through this instance\n        are not affected.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_layer_surface_v1\" version=\"5\">\n    <description summary=\"layer metadata interface\">\n      An interface that may be implemented by a wl_surface, for surfaces that\n      are designed to be rendered as a layer of a stacked desktop-like\n      environment.\n\n      Layer surface state (layer, size, anchor, exclusive zone,\n      margin, interactivity) is double-buffered, and will be applied at the\n      time wl_surface.commit of the corresponding wl_surface is called.\n\n      Attaching a null buffer to a layer surface unmaps it.\n\n      Unmapping a layer_surface means that the surface cannot be shown by the\n      compositor until it is explicitly mapped again. The layer_surface\n      returns to the state it had right after layer_shell.get_layer_surface.\n      The client can re-map the surface by performing a commit without any\n      buffer attached, waiting for a configure event and handling it as usual.\n    </description>\n\n    <request name=\"set_size\">\n      <description summary=\"sets the size of the surface\">\n        Sets the size of the surface in surface-local coordinates. The\n        compositor will display the surface centered with respect to its\n        anchors.\n\n        If you pass 0 for either value, the compositor will assign it and\n        inform you of the assignment in the configure event. You must set your\n        anchor to opposite edges in the dimensions you omit; not doing so is a\n        protocol error. Both values are 0 by default.\n\n        Size is double-buffered, see wl_surface.commit.\n      </description>\n      <arg name=\"width\" type=\"uint\"/>\n      <arg name=\"height\" type=\"uint\"/>\n    </request>\n\n    <request name=\"set_anchor\">\n      <description summary=\"configures the anchor point of the surface\">\n        Requests that the compositor anchor the surface to the specified edges\n        and corners. If two orthogonal edges are specified (e.g. 'top' and\n        'left'), then the anchor point will be the intersection of the edges\n        (e.g. the top left corner of the output); otherwise the anchor point\n        will be centered on that edge, or in the center if none is specified.\n\n        Anchor is double-buffered, see wl_surface.commit.\n      </description>\n      <arg name=\"anchor\" type=\"uint\" enum=\"anchor\"/>\n    </request>\n\n    <request name=\"set_exclusive_zone\">\n      <description summary=\"configures the exclusive geometry of this surface\">\n        Requests that the compositor avoids occluding an area with other\n        surfaces. The compositor's use of this information is\n        implementation-dependent - do not assume that this region will not\n        actually be occluded.\n\n        A positive value is only meaningful if the surface is anchored to one\n        edge or an edge and both perpendicular edges. If the surface is not\n        anchored, anchored to only two perpendicular edges (a corner), anchored\n        to only two parallel edges or anchored to all edges, a positive value\n        will be treated the same as zero.\n\n        A positive zone is the distance from the edge in surface-local\n        coordinates to consider exclusive.\n\n        Surfaces that do not wish to have an exclusive zone may instead specify\n        how they should interact with surfaces that do. If set to zero, the\n        surface indicates that it would like to be moved to avoid occluding\n        surfaces with a positive exclusive zone. If set to -1, the surface\n        indicates that it would not like to be moved to accommodate for other\n        surfaces, and the compositor should extend it all the way to the edges\n        it is anchored to.\n\n        For example, a panel might set its exclusive zone to 10, so that\n        maximized shell surfaces are not shown on top of it. A notification\n        might set its exclusive zone to 0, so that it is moved to avoid\n        occluding the panel, but shell surfaces are shown underneath it. A\n        wallpaper or lock screen might set their exclusive zone to -1, so that\n        they stretch below or over the panel.\n\n        The default value is 0.\n\n        Exclusive zone is double-buffered, see wl_surface.commit.\n      </description>\n      <arg name=\"zone\" type=\"int\"/>\n    </request>\n\n    <request name=\"set_margin\">\n      <description summary=\"sets a margin from the anchor point\">\n        Requests that the surface be placed some distance away from the anchor\n        point on the output, in surface-local coordinates. Setting this value\n        for edges you are not anchored to has no effect.\n\n        The exclusive zone includes the margin.\n\n        Margin is double-buffered, see wl_surface.commit.\n      </description>\n      <arg name=\"top\" type=\"int\"/>\n      <arg name=\"right\" type=\"int\"/>\n      <arg name=\"bottom\" type=\"int\"/>\n      <arg name=\"left\" type=\"int\"/>\n    </request>\n\n    <enum name=\"keyboard_interactivity\">\n      <description summary=\"types of keyboard interaction possible for a layer shell surface\">\n        Types of keyboard interaction possible for layer shell surfaces. The\n        rationale for this is twofold: (1) some applications are not interested\n        in keyboard events and not allowing them to be focused can improve the\n        desktop experience; (2) some applications will want to take exclusive\n        keyboard focus.\n      </description>\n\n      <entry name=\"none\" value=\"0\">\n        <description summary=\"no keyboard focus is possible\">\n          This value indicates that this surface is not interested in keyboard\n          events and the compositor should never assign it the keyboard focus.\n\n          This is the default value, set for newly created layer shell surfaces.\n\n          This is useful for e.g. desktop widgets that display information or\n          only have interaction with non-keyboard input devices.\n        </description>\n      </entry>\n      <entry name=\"exclusive\" value=\"1\">\n        <description summary=\"request exclusive keyboard focus\">\n          Request exclusive keyboard focus if this surface is above the shell surface layer.\n\n          For the top and overlay layers, the seat will always give\n          exclusive keyboard focus to the top-most layer which has keyboard\n          interactivity set to exclusive. If this layer contains multiple\n          surfaces with keyboard interactivity set to exclusive, the compositor\n          determines the one receiving keyboard events in an implementation-\n          defined manner. In this case, no guarantee is made when this surface\n          will receive keyboard focus (if ever).\n\n          For the bottom and background layers, the compositor is allowed to use\n          normal focus semantics.\n\n          This setting is mainly intended for applications that need to ensure\n          they receive all keyboard events, such as a lock screen or a password\n          prompt.\n        </description>\n      </entry>\n      <entry name=\"on_demand\" value=\"2\" since=\"4\">\n        <description summary=\"request regular keyboard focus semantics\">\n          This requests the compositor to allow this surface to be focused and\n          unfocused by the user in an implementation-defined manner. The user\n          should be able to unfocus this surface even regardless of the layer\n          it is on.\n\n          Typically, the compositor will want to use its normal mechanism to\n          manage keyboard focus between layer shell surfaces with this setting\n          and regular toplevels on the desktop layer (e.g. click to focus).\n          Nevertheless, it is possible for a compositor to require a special\n          interaction to focus or unfocus layer shell surfaces (e.g. requiring\n          a click even if focus follows the mouse normally, or providing a\n          keybinding to switch focus between layers).\n\n          This setting is mainly intended for desktop shell components (e.g.\n          panels) that allow keyboard interaction. Using this option can allow\n          implementing a desktop shell that can be fully usable without the\n          mouse.\n        </description>\n      </entry>\n    </enum>\n\n    <request name=\"set_keyboard_interactivity\">\n      <description summary=\"requests keyboard events\">\n        Set how keyboard events are delivered to this surface. By default,\n        layer shell surfaces do not receive keyboard events; this request can\n        be used to change this.\n\n        This setting is inherited by child surfaces set by the get_popup\n        request.\n\n        Layer surfaces receive pointer, touch, and tablet events normally. If\n        you do not want to receive them, set the input region on your surface\n        to an empty region.\n\n        Keyboard interactivity is double-buffered, see wl_surface.commit.\n      </description>\n      <arg name=\"keyboard_interactivity\" type=\"uint\" enum=\"keyboard_interactivity\"/>\n    </request>\n\n    <request name=\"get_popup\">\n      <description summary=\"assign this layer_surface as an xdg_popup parent\">\n        This assigns an xdg_popup's parent to this layer_surface.  This popup\n        should have been created via xdg_surface::get_popup with the parent set\n        to NULL, and this request must be invoked before committing the popup's\n        initial state.\n\n        See the documentation of xdg_popup for more details about what an\n        xdg_popup is and how it is used.\n      </description>\n      <arg name=\"popup\" type=\"object\" interface=\"xdg_popup\"/>\n    </request>\n\n    <request name=\"ack_configure\">\n      <description summary=\"ack a configure event\">\n        When a configure event is received, if a client commits the\n        surface in response to the configure event, then the client\n        must make an ack_configure request sometime before the commit\n        request, passing along the serial of the configure event.\n\n        If the client receives multiple configure events before it\n        can respond to one, it only has to ack the last configure event.\n\n        A client is not required to commit immediately after sending\n        an ack_configure request - it may even ack_configure several times\n        before its next surface commit.\n\n        A client may send multiple ack_configure requests before committing, but\n        only the last request sent before a commit indicates which configure\n        event the client really is responding to.\n      </description>\n      <arg name=\"serial\" type=\"uint\" summary=\"the serial from the configure event\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the layer_surface\">\n        This request destroys the layer surface.\n      </description>\n    </request>\n\n    <event name=\"configure\">\n      <description summary=\"suggest a surface change\">\n        The configure event asks the client to resize its surface.\n\n        Clients should arrange their surface for the new states, and then send\n        an ack_configure request with the serial sent in this configure event at\n        some point before committing the new surface.\n\n        The client is free to dismiss all but the last configure event it\n        received.\n\n        The width and height arguments specify the size of the window in\n        surface-local coordinates.\n\n        The size is a hint, in the sense that the client is free to ignore it if\n        it doesn't resize, pick a smaller size (to satisfy aspect ratio or\n        resize in steps of NxM pixels). If the client picks a smaller size and\n        is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the\n        surface will be centered on this axis.\n\n        If the width or height arguments are zero, it means the client should\n        decide its own window dimension.\n      </description>\n      <arg name=\"serial\" type=\"uint\"/>\n      <arg name=\"width\" type=\"uint\"/>\n      <arg name=\"height\" type=\"uint\"/>\n    </event>\n\n    <event name=\"closed\">\n      <description summary=\"surface should be closed\">\n        The closed event is sent by the compositor when the surface will no\n        longer be shown. The output may have been destroyed or the user may\n        have asked for it to be removed. Further changes to the surface will be\n        ignored. The client should destroy the resource after receiving this\n        event, and create a new surface if they so choose.\n      </description>\n    </event>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_surface_state\" value=\"0\" summary=\"provided surface state is invalid\"/>\n      <entry name=\"invalid_size\" value=\"1\" summary=\"size is invalid\"/>\n      <entry name=\"invalid_anchor\" value=\"2\" summary=\"anchor bitfield is invalid\"/>\n      <entry name=\"invalid_keyboard_interactivity\" value=\"3\" summary=\"keyboard interactivity is invalid\"/>\n      <entry name=\"invalid_exclusive_edge\" value=\"4\" summary=\"exclusive edge is invalid given the surface anchors\"/>\n    </enum>\n\n    <enum name=\"anchor\" bitfield=\"true\">\n      <entry name=\"top\" value=\"1\" summary=\"the top edge of the anchor rectangle\"/>\n      <entry name=\"bottom\" value=\"2\" summary=\"the bottom edge of the anchor rectangle\"/>\n      <entry name=\"left\" value=\"4\" summary=\"the left edge of the anchor rectangle\"/>\n      <entry name=\"right\" value=\"8\" summary=\"the right edge of the anchor rectangle\"/>\n    </enum>\n\n    <!-- Version 2 additions -->\n\n    <request name=\"set_layer\" since=\"2\">\n      <description summary=\"change the layer of the surface\">\n        Change the layer that the surface is rendered on.\n\n        Layer is double-buffered, see wl_surface.commit.\n      </description>\n      <arg name=\"layer\" type=\"uint\" enum=\"zwlr_layer_shell_v1.layer\" summary=\"layer to move this surface to\"/>\n    </request>\n\n    <!-- Version 5 additions -->\n\n    <request name=\"set_exclusive_edge\" since=\"5\">\n      <description summary=\"set the edge the exclusive zone will be applied to\">\n        Requests an edge for the exclusive zone to apply. The exclusive\n        edge will be automatically deduced from anchor points when possible,\n        but when the surface is anchored to a corner, it will be necessary\n        to set it explicitly to disambiguate, as it is not possible to deduce\n        which one of the two corner edges should be used.\n\n        The edge must be one the surface is anchored to, otherwise the\n        invalid_exclusive_edge protocol error will be raised.\n      </description>\n      <arg name=\"edge\" type=\"uint\" enum=\"anchor\"/>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-output-management-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_output_management_unstable_v1\">\n  <copyright>\n    Copyright © 2019 Purism SPC\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <description summary=\"protocol to configure output devices\">\n    This protocol exposes interfaces to obtain and modify output device\n    configuration.\n\n    Warning! The protocol described in this file is experimental and\n    backward incompatible changes may be made. Backward compatible changes\n    may be added together with the corresponding interface version bump.\n    Backward incompatible changes are done by bumping the version number in\n    the protocol and interface names and resetting the interface version.\n    Once the protocol is to be declared stable, the 'z' prefix and the\n    version number in the protocol and interface names are removed and the\n    interface version number is reset.\n  </description>\n\n  <interface name=\"zwlr_output_manager_v1\" version=\"4\">\n    <description summary=\"output device configuration manager\">\n      This interface is a manager that allows reading and writing the current\n      output device configuration.\n\n      Output devices that display pixels (e.g. a physical monitor or a virtual\n      output in a window) are represented as heads. Heads cannot be created nor\n      destroyed by the client, but they can be enabled or disabled and their\n      properties can be changed. Each head may have one or more available modes.\n\n      Whenever a head appears (e.g. a monitor is plugged in), it will be\n      advertised via the head event. Immediately after the output manager is\n      bound, all current heads are advertised.\n\n      Whenever a head's properties change, the relevant wlr_output_head events\n      will be sent. Not all head properties will be sent: only properties that\n      have changed need to.\n\n      Whenever a head disappears (e.g. a monitor is unplugged), a\n      wlr_output_head.finished event will be sent.\n\n      After one or more heads appear, change or disappear, the done event will\n      be sent. It carries a serial which can be used in a create_configuration\n      request to update heads properties.\n\n      The information obtained from this protocol should only be used for output\n      configuration purposes. This protocol is not designed to be a generic\n      output property advertisement protocol for regular clients. Instead,\n      protocols such as xdg-output should be used.\n    </description>\n\n    <event name=\"head\">\n      <description summary=\"introduce a new head\">\n        This event introduces a new head. This happens whenever a new head\n        appears (e.g. a monitor is plugged in) or after the output manager is\n        bound.\n      </description>\n      <arg name=\"head\" type=\"new_id\" interface=\"zwlr_output_head_v1\"/>\n    </event>\n\n    <event name=\"done\">\n      <description summary=\"sent all information about current configuration\">\n        This event is sent after all information has been sent after binding to\n        the output manager object and after any subsequent changes. This applies\n        to child head and mode objects as well. In other words, this event is\n        sent whenever a head or mode is created or destroyed and whenever one of\n        their properties has been changed. Not all state is re-sent each time\n        the current configuration changes: only the actual changes are sent.\n\n        This allows changes to the output configuration to be seen as atomic,\n        even if they happen via multiple events.\n\n        A serial is sent to be used in a future create_configuration request.\n      </description>\n      <arg name=\"serial\" type=\"uint\" summary=\"current configuration serial\"/>\n    </event>\n\n    <request name=\"create_configuration\">\n      <description summary=\"create a new output configuration object\">\n        Create a new output configuration object. This allows to update head\n        properties.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_output_configuration_v1\"/>\n      <arg name=\"serial\" type=\"uint\"/>\n    </request>\n\n    <request name=\"stop\">\n      <description summary=\"stop sending events\">\n        Indicates the client no longer wishes to receive events for output\n        configuration changes. However the compositor may emit further events,\n        until the finished event is emitted.\n\n        The client must not send any more requests after this one.\n      </description>\n    </request>\n\n    <event name=\"finished\" type=\"destructor\">\n      <description summary=\"the compositor has finished with the manager\">\n        This event indicates that the compositor is done sending manager events.\n        The compositor will destroy the object immediately after sending this\n        event, so it will become invalid and the client should release any\n        resources associated with it.\n      </description>\n    </event>\n  </interface>\n\n  <interface name=\"zwlr_output_head_v1\" version=\"4\">\n    <description summary=\"output device\">\n      A head is an output device. The difference between a wl_output object and\n      a head is that heads are advertised even if they are turned off. A head\n      object only advertises properties and cannot be used directly to change\n      them.\n\n      A head has some read-only properties: modes, name, description and\n      physical_size. These cannot be changed by clients.\n\n      Other properties can be updated via a wlr_output_configuration object.\n\n      Properties sent via this interface are applied atomically via the\n      wlr_output_manager.done event. No guarantees are made regarding the order\n      in which properties are sent.\n    </description>\n\n    <event name=\"name\">\n      <description summary=\"head name\">\n        This event describes the head name.\n\n        The naming convention is compositor defined, but limited to alphanumeric\n        characters and dashes (-). Each name is unique among all wlr_output_head\n        objects, but if a wlr_output_head object is destroyed the same name may\n        be reused later. The names will also remain consistent across sessions\n        with the same hardware and software configuration.\n\n        Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do\n        not assume that the name is a reflection of an underlying DRM\n        connector, X11 connection, etc.\n\n        If the compositor implements the xdg-output protocol and this head is\n        enabled, the xdg_output.name event must report the same name.\n\n        The name event is sent after a wlr_output_head object is created. This\n        event is only sent once per object, and the name does not change over\n        the lifetime of the wlr_output_head object.\n      </description>\n      <arg name=\"name\" type=\"string\"/>\n    </event>\n\n    <event name=\"description\">\n      <description summary=\"head description\">\n        This event describes a human-readable description of the head.\n\n        The description is a UTF-8 string with no convention defined for its\n        contents. Examples might include 'Foocorp 11\" Display' or 'Virtual X11\n        output via :1'. However, do not assume that the name is a reflection of\n        the make, model, serial of the underlying DRM connector or the display\n        name of the underlying X11 connection, etc.\n\n        If the compositor implements xdg-output and this head is enabled,\n        the xdg_output.description must report the same description.\n\n        The description event is sent after a wlr_output_head object is created.\n        This event is only sent once per object, and the description does not\n        change over the lifetime of the wlr_output_head object.\n      </description>\n      <arg name=\"description\" type=\"string\"/>\n    </event>\n\n    <event name=\"physical_size\">\n      <description summary=\"head physical size\">\n        This event describes the physical size of the head. This event is only\n        sent if the head has a physical size (e.g. is not a projector or a\n        virtual device).\n      </description>\n      <arg name=\"width\" type=\"int\" summary=\"width in millimeters of the output\"/>\n      <arg name=\"height\" type=\"int\" summary=\"height in millimeters of the output\"/>\n    </event>\n\n    <event name=\"mode\">\n      <description summary=\"introduce a mode\">\n        This event introduces a mode for this head. It is sent once per\n        supported mode.\n      </description>\n      <arg name=\"mode\" type=\"new_id\" interface=\"zwlr_output_mode_v1\"/>\n    </event>\n\n    <event name=\"enabled\">\n      <description summary=\"head is enabled or disabled\">\n        This event describes whether the head is enabled. A disabled head is not\n        mapped to a region of the global compositor space.\n\n        When a head is disabled, some properties (current_mode, position,\n        transform and scale) are irrelevant.\n      </description>\n      <arg name=\"enabled\" type=\"int\" summary=\"zero if disabled, non-zero if enabled\"/>\n    </event>\n\n    <event name=\"current_mode\">\n      <description summary=\"current mode\">\n        This event describes the mode currently in use for this head. It is only\n        sent if the output is enabled.\n      </description>\n      <arg name=\"mode\" type=\"object\" interface=\"zwlr_output_mode_v1\"/>\n    </event>\n\n    <event name=\"position\">\n      <description summary=\"current position\">\n        This events describes the position of the head in the global compositor\n        space. It is only sent if the output is enabled.\n      </description>\n      <arg name=\"x\" type=\"int\"\n        summary=\"x position within the global compositor space\"/>\n      <arg name=\"y\" type=\"int\"\n        summary=\"y position within the global compositor space\"/>\n    </event>\n\n    <event name=\"transform\">\n      <description summary=\"current transformation\">\n        This event describes the transformation currently applied to the head.\n        It is only sent if the output is enabled.\n      </description>\n      <arg name=\"transform\" type=\"int\" enum=\"wl_output.transform\"/>\n    </event>\n\n    <event name=\"scale\">\n      <description summary=\"current scale\">\n        This events describes the scale of the head in the global compositor\n        space. It is only sent if the output is enabled.\n      </description>\n      <arg name=\"scale\" type=\"fixed\"/>\n    </event>\n\n    <event name=\"finished\">\n      <description summary=\"the head has disappeared\">\n        This event indicates that the head is no longer available. The head\n        object becomes inert. Clients should send a destroy request and release\n        any resources associated with it.\n      </description>\n    </event>\n\n    <!-- Version 2 additions -->\n\n    <event name=\"make\" since=\"2\">\n      <description summary=\"head manufacturer\">\n        This event describes the manufacturer of the head.\n\n        This must report the same make as the wl_output interface does in its\n        geometry event.\n\n        Together with the model and serial_number events the purpose is to\n        allow clients to recognize heads from previous sessions and for example\n        load head-specific configurations back.\n\n        It is not guaranteed this event will be ever sent. A reason for that\n        can be that the compositor does not have information about the make of\n        the head or the definition of a make is not sensible in the current\n        setup, for example in a virtual session. Clients can still try to\n        identify the head by available information from other events but should\n        be aware that there is an increased risk of false positives.\n\n        It is not recommended to display the make string in UI to users. For\n        that the string provided by the description event should be preferred.\n      </description>\n      <arg name=\"make\" type=\"string\"/>\n    </event>\n\n    <event name=\"model\" since=\"2\">\n      <description summary=\"head model\">\n        This event describes the model of the head.\n\n        This must report the same model as the wl_output interface does in its\n        geometry event.\n\n        Together with the make and serial_number events the purpose is to\n        allow clients to recognize heads from previous sessions and for example\n        load head-specific configurations back.\n\n        It is not guaranteed this event will be ever sent. A reason for that\n        can be that the compositor does not have information about the model of\n        the head or the definition of a model is not sensible in the current\n        setup, for example in a virtual session. Clients can still try to\n        identify the head by available information from other events but should\n        be aware that there is an increased risk of false positives.\n\n        It is not recommended to display the model string in UI to users. For\n        that the string provided by the description event should be preferred.\n      </description>\n      <arg name=\"model\" type=\"string\"/>\n    </event>\n\n    <event name=\"serial_number\" since=\"2\">\n      <description summary=\"head serial number\">\n        This event describes the serial number of the head.\n\n        Together with the make and model events the purpose is to allow clients\n        to recognize heads from previous sessions and for example load head-\n        specific configurations back.\n\n        It is not guaranteed this event will be ever sent. A reason for that\n        can be that the compositor does not have information about the serial\n        number of the head or the definition of a serial number is not sensible\n        in the current setup. Clients can still try to identify the head by\n        available information from other events but should be aware that there\n        is an increased risk of false positives.\n\n        It is not recommended to display the serial_number string in UI to\n        users. For that the string provided by the description event should be\n        preferred.\n      </description>\n      <arg name=\"serial_number\" type=\"string\"/>\n    </event>\n\n    <!-- Version 3 additions -->\n\n    <request name=\"release\" type=\"destructor\" since=\"3\">\n      <description summary=\"destroy the head object\">\n        This request indicates that the client will no longer use this head\n        object.\n      </description>\n    </request>\n\n    <!-- Version 4 additions -->\n\n    <enum name=\"adaptive_sync_state\" since=\"4\">\n      <entry name=\"disabled\" value=\"0\" summary=\"adaptive sync is disabled\"/>\n      <entry name=\"enabled\" value=\"1\" summary=\"adaptive sync is enabled\"/>\n    </enum>\n\n    <event name=\"adaptive_sync\" since=\"4\">\n      <description summary=\"current adaptive sync state\">\n        This event describes whether adaptive sync is currently enabled for\n        the head or not. Adaptive sync is also known as Variable Refresh\n        Rate or VRR.\n      </description>\n      <arg name=\"state\" type=\"uint\" enum=\"adaptive_sync_state\"/>\n    </event>\n  </interface>\n\n  <interface name=\"zwlr_output_mode_v1\" version=\"3\">\n    <description summary=\"output mode\">\n      This object describes an output mode.\n\n      Some heads don't support output modes, in which case modes won't be\n      advertised.\n\n      Properties sent via this interface are applied atomically via the\n      wlr_output_manager.done event. No guarantees are made regarding the order\n      in which properties are sent.\n    </description>\n\n    <event name=\"size\">\n      <description summary=\"mode size\">\n        This event describes the mode size. The size is given in physical\n        hardware units of the output device. This is not necessarily the same as\n        the output size in the global compositor space. For instance, the output\n        may be scaled or transformed.\n      </description>\n      <arg name=\"width\" type=\"int\" summary=\"width of the mode in hardware units\"/>\n      <arg name=\"height\" type=\"int\" summary=\"height of the mode in hardware units\"/>\n    </event>\n\n    <event name=\"refresh\">\n      <description summary=\"mode refresh rate\">\n        This event describes the mode's fixed vertical refresh rate. It is only\n        sent if the mode has a fixed refresh rate.\n      </description>\n      <arg name=\"refresh\" type=\"int\" summary=\"vertical refresh rate in mHz\"/>\n    </event>\n\n    <event name=\"preferred\">\n      <description summary=\"mode is preferred\">\n        This event advertises this mode as preferred.\n      </description>\n    </event>\n\n    <event name=\"finished\">\n      <description summary=\"the mode has disappeared\">\n        This event indicates that the mode is no longer available. The mode\n        object becomes inert. Clients should send a destroy request and release\n        any resources associated with it.\n      </description>\n    </event>\n\n    <!-- Version 3 additions -->\n\n    <request name=\"release\" type=\"destructor\" since=\"3\">\n      <description summary=\"destroy the mode object\">\n        This request indicates that the client will no longer use this mode\n        object.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_output_configuration_v1\" version=\"4\">\n    <description summary=\"output configuration\">\n      This object is used by the client to describe a full output configuration.\n\n      First, the client needs to setup the output configuration. Each head can\n      be either enabled (and configured) or disabled. It is a protocol error to\n      send two enable_head or disable_head requests with the same head. It is a\n      protocol error to omit a head in a configuration.\n\n      Then, the client can apply or test the configuration. The compositor will\n      then reply with a succeeded, failed or cancelled event. Finally the client\n      should destroy the configuration object.\n    </description>\n\n    <enum name=\"error\">\n      <entry name=\"already_configured_head\" value=\"1\"\n        summary=\"head has been configured twice\"/>\n      <entry name=\"unconfigured_head\" value=\"2\"\n        summary=\"head has not been configured\"/>\n      <entry name=\"already_used\" value=\"3\"\n        summary=\"request sent after configuration has been applied or tested\"/>\n    </enum>\n\n    <request name=\"enable_head\">\n      <description summary=\"enable and configure a head\">\n        Enable a head. This request creates a head configuration object that can\n        be used to change the head's properties.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_output_configuration_head_v1\"\n        summary=\"a new object to configure the head\"/>\n      <arg name=\"head\" type=\"object\" interface=\"zwlr_output_head_v1\"\n        summary=\"the head to be enabled\"/>\n    </request>\n\n    <request name=\"disable_head\">\n      <description summary=\"disable a head\">\n        Disable a head.\n      </description>\n      <arg name=\"head\" type=\"object\" interface=\"zwlr_output_head_v1\"\n        summary=\"the head to be disabled\"/>\n    </request>\n\n    <request name=\"apply\">\n      <description summary=\"apply the configuration\">\n        Apply the new output configuration.\n\n        In case the configuration is successfully applied, there is no guarantee\n        that the new output state matches completely the requested\n        configuration. For instance, a compositor might round the scale if it\n        doesn't support fractional scaling.\n\n        After this request has been sent, the compositor must respond with an\n        succeeded, failed or cancelled event. Sending a request that isn't the\n        destructor is a protocol error.\n      </description>\n    </request>\n\n    <request name=\"test\">\n      <description summary=\"test the configuration\">\n        Test the new output configuration. The configuration won't be applied,\n        but will only be validated.\n\n        Even if the compositor succeeds to test a configuration, applying it may\n        fail.\n\n        After this request has been sent, the compositor must respond with an\n        succeeded, failed or cancelled event. Sending a request that isn't the\n        destructor is a protocol error.\n      </description>\n    </request>\n\n    <event name=\"succeeded\">\n      <description summary=\"configuration changes succeeded\">\n        Sent after the compositor has successfully applied the changes or\n        tested them.\n\n        Upon receiving this event, the client should destroy this object.\n\n        If the current configuration has changed, events to describe the changes\n        will be sent followed by a wlr_output_manager.done event.\n      </description>\n    </event>\n\n    <event name=\"failed\">\n      <description summary=\"configuration changes failed\">\n        Sent if the compositor rejects the changes or failed to apply them. The\n        compositor should revert any changes made by the apply request that\n        triggered this event.\n\n        Upon receiving this event, the client should destroy this object.\n      </description>\n    </event>\n\n    <event name=\"cancelled\">\n      <description summary=\"configuration has been cancelled\">\n        Sent if the compositor cancels the configuration because the state of an\n        output changed and the client has outdated information (e.g. after an\n        output has been hotplugged).\n\n        The client can create a new configuration with a newer serial and try\n        again.\n\n        Upon receiving this event, the client should destroy this object.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the output configuration\">\n        Using this request a client can tell the compositor that it is not going\n        to use the configuration object anymore. Any changes to the outputs\n        that have not been applied will be discarded.\n\n        This request also destroys wlr_output_configuration_head objects created\n        via this object.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_output_configuration_head_v1\" version=\"4\">\n    <description summary=\"head configuration\">\n      This object is used by the client to update a single head's configuration.\n\n      It is a protocol error to set the same property twice.\n    </description>\n\n    <enum name=\"error\">\n      <entry name=\"already_set\" value=\"1\" summary=\"property has already been set\"/>\n      <entry name=\"invalid_mode\" value=\"2\" summary=\"mode doesn't belong to head\"/>\n      <entry name=\"invalid_custom_mode\" value=\"3\" summary=\"mode is invalid\"/>\n      <entry name=\"invalid_transform\" value=\"4\" summary=\"transform value outside enum\"/>\n      <entry name=\"invalid_scale\" value=\"5\" summary=\"scale negative or zero\"/>\n      <entry name=\"invalid_adaptive_sync_state\" value=\"6\" since=\"4\"\n        summary=\"invalid enum value used in the set_adaptive_sync request\"/>\n    </enum>\n\n    <request name=\"set_mode\">\n      <description summary=\"set the mode\">\n        This request sets the head's mode.\n      </description>\n      <arg name=\"mode\" type=\"object\" interface=\"zwlr_output_mode_v1\"/>\n    </request>\n\n    <request name=\"set_custom_mode\">\n      <description summary=\"set a custom mode\">\n        This request assigns a custom mode to the head. The size is given in\n        physical hardware units of the output device. If set to zero, the\n        refresh rate is unspecified.\n\n        It is a protocol error to set both a mode and a custom mode.\n      </description>\n      <arg name=\"width\" type=\"int\" summary=\"width of the mode in hardware units\"/>\n      <arg name=\"height\" type=\"int\" summary=\"height of the mode in hardware units\"/>\n      <arg name=\"refresh\" type=\"int\" summary=\"vertical refresh rate in mHz or zero\"/>\n    </request>\n\n    <request name=\"set_position\">\n      <description summary=\"set the position\">\n        This request sets the head's position in the global compositor space.\n      </description>\n      <arg name=\"x\" type=\"int\" summary=\"x position in the global compositor space\"/>\n      <arg name=\"y\" type=\"int\" summary=\"y position in the global compositor space\"/>\n    </request>\n\n    <request name=\"set_transform\">\n      <description summary=\"set the transform\">\n        This request sets the head's transform.\n      </description>\n      <arg name=\"transform\" type=\"int\" enum=\"wl_output.transform\"/>\n    </request>\n\n    <request name=\"set_scale\">\n      <description summary=\"set the scale\">\n        This request sets the head's scale.\n      </description>\n      <arg name=\"scale\" type=\"fixed\"/>\n    </request>\n\n    <!-- Version 4 additions -->\n\n    <request name=\"set_adaptive_sync\" since=\"4\">\n      <description summary=\"enable/disable adaptive sync\">\n        This request enables/disables adaptive sync. Adaptive sync is also\n        known as Variable Refresh Rate or VRR.\n      </description>\n      <arg name=\"state\" type=\"uint\" enum=\"zwlr_output_head_v1.adaptive_sync_state\"/>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-output-power-management-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_output_power_management_unstable_v1\">\n  <copyright>\n    Copyright © 2019 Purism SPC\n\n    Permission is hereby granted, free of charge, to any person obtaining a\n    copy of this software and associated documentation files (the \"Software\"),\n    to deal in the Software without restriction, including without limitation\n    the rights to use, copy, modify, merge, publish, distribute, sublicense,\n    and/or sell copies of the Software, and to permit persons to whom the\n    Software is furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice (including the next\n    paragraph) shall be included in all copies or substantial portions of the\n    Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n    DEALINGS IN THE SOFTWARE.\n  </copyright>\n\n  <description summary=\"Control power management modes of outputs\">\n    This protocol allows clients to control power management modes\n    of outputs that are currently part of the compositor space. The\n    intent is to allow special clients like desktop shells to power\n    down outputs when the system is idle.\n\n    To modify outputs not currently part of the compositor space see\n    wlr-output-management.\n\n    Warning! The protocol described in this file is experimental and\n    backward incompatible changes may be made. Backward compatible changes\n    may be added together with the corresponding interface version bump.\n    Backward incompatible changes are done by bumping the version number in\n    the protocol and interface names and resetting the interface version.\n    Once the protocol is to be declared stable, the 'z' prefix and the\n    version number in the protocol and interface names are removed and the\n    interface version number is reset.\n  </description>\n\n  <interface name=\"zwlr_output_power_manager_v1\" version=\"1\">\n    <description summary=\"manager to create per-output power management\">\n      This interface is a manager that allows creating per-output power\n      management mode controls.\n    </description>\n\n    <request name=\"get_output_power\">\n      <description summary=\"get a power management for an output\">\n        Create a output power management mode control that can be used to\n        adjust the power management mode for a given output.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_output_power_v1\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the manager\">\n        All objects created by the manager will still remain valid, until their\n        appropriate destroy request has been called.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_output_power_v1\" version=\"1\">\n    <description summary=\"adjust power management mode for an output\">\n      This object offers requests to set the power management mode of\n      an output.\n    </description>\n\n    <enum name=\"mode\">\n      <entry name=\"off\" value=\"0\"\n             summary=\"Output is turned off.\"/>\n      <entry name=\"on\" value=\"1\"\n             summary=\"Output is turned on, no power saving\"/>\n    </enum>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_mode\" value=\"1\" summary=\"inexistent power save mode\"/>\n    </enum>\n\n    <request name=\"set_mode\">\n      <description summary=\"Set an outputs power save mode\">\n        Set an output's power save mode to the given mode. The mode change\n        is effective immediately. If the output does not support the given\n        mode a failed event is sent.\n      </description>\n      <arg name=\"mode\" type=\"uint\" enum=\"mode\" summary=\"the power save mode to set\"/>\n    </request>\n\n    <event name=\"mode\">\n      <description summary=\"Report a power management mode change\">\n        Report the power management mode change of an output.\n\n        The mode event is sent after an output changed its power\n        management mode. The reason can be a client using set_mode or the\n        compositor deciding to change an output's mode.\n        This event is also sent immediately when the object is created\n        so the client is informed about the current power management mode.\n      </description>\n      <arg name=\"mode\" type=\"uint\" enum=\"mode\"\n           summary=\"the output's new power management mode\"/>\n    </event>\n\n    <event name=\"failed\">\n      <description summary=\"object no longer valid\">\n        This event indicates that the output power management mode control\n        is no longer valid. This can happen for a number of reasons,\n        including:\n        - The output doesn't support power management\n        - Another client already has exclusive power management mode control\n          for this output\n        - The output disappeared\n\n        Upon receiving this event, the client should destroy this object.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy this power management\">\n        Destroys the output power management mode control object.\n      </description>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-screencopy-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_screencopy_unstable_v1\">\n  <copyright>\n    Copyright © 2018 Simon Ser\n    Copyright © 2019 Andri Yngvason\n\n    Permission is hereby granted, free of charge, to any person obtaining a\n    copy of this software and associated documentation files (the \"Software\"),\n    to deal in the Software without restriction, including without limitation\n    the rights to use, copy, modify, merge, publish, distribute, sublicense,\n    and/or sell copies of the Software, and to permit persons to whom the\n    Software is furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice (including the next\n    paragraph) shall be included in all copies or substantial portions of the\n    Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n    DEALINGS IN THE SOFTWARE.\n  </copyright>\n\n  <description summary=\"screen content capturing on client buffers\">\n    This protocol allows clients to ask the compositor to copy part of the\n    screen content to a client buffer.\n\n    Warning! The protocol described in this file is experimental and\n    backward incompatible changes may be made. Backward compatible changes\n    may be added together with the corresponding interface version bump.\n    Backward incompatible changes are done by bumping the version number in\n    the protocol and interface names and resetting the interface version.\n    Once the protocol is to be declared stable, the 'z' prefix and the\n    version number in the protocol and interface names are removed and the\n    interface version number is reset.\n  </description>\n\n  <interface name=\"zwlr_screencopy_manager_v1\" version=\"3\">\n    <description summary=\"manager to inform clients and begin capturing\">\n      This object is a manager which offers requests to start capturing from a\n      source.\n    </description>\n\n    <request name=\"capture_output\">\n      <description summary=\"capture an output\">\n        Capture the next frame of an entire output.\n      </description>\n      <arg name=\"frame\" type=\"new_id\" interface=\"zwlr_screencopy_frame_v1\"/>\n      <arg name=\"overlay_cursor\" type=\"int\"\n        summary=\"composite cursor onto the frame\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </request>\n\n    <request name=\"capture_output_region\">\n      <description summary=\"capture an output's region\">\n        Capture the next frame of an output's region.\n\n        The region is given in output logical coordinates, see\n        xdg_output.logical_size. The region will be clipped to the output's\n        extents.\n      </description>\n      <arg name=\"frame\" type=\"new_id\" interface=\"zwlr_screencopy_frame_v1\"/>\n      <arg name=\"overlay_cursor\" type=\"int\"\n        summary=\"composite cursor onto the frame\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n      <arg name=\"x\" type=\"int\"/>\n      <arg name=\"y\" type=\"int\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the manager\">\n        All objects created by the manager will still remain valid, until their\n        appropriate destroy request has been called.\n      </description>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_screencopy_frame_v1\" version=\"3\">\n    <description summary=\"a frame ready for copy\">\n      This object represents a single frame.\n\n      When created, a series of buffer events will be sent, each representing a\n      supported buffer type. The \"buffer_done\" event is sent afterwards to\n      indicate that all supported buffer types have been enumerated. The client\n      will then be able to send a \"copy\" request. If the capture is successful,\n      the compositor will send a \"flags\" followed by a \"ready\" event.\n\n      For objects version 2 or lower, wl_shm buffers are always supported, ie.\n      the \"buffer\" event is guaranteed to be sent.\n\n      If the capture failed, the \"failed\" event is sent. This can happen anytime\n      before the \"ready\" event.\n\n      Once either a \"ready\" or a \"failed\" event is received, the client should\n      destroy the frame.\n    </description>\n\n    <event name=\"buffer\">\n      <description summary=\"wl_shm buffer information\">\n        Provides information about wl_shm buffer parameters that need to be\n        used for this frame. This event is sent once after the frame is created\n        if wl_shm buffers are supported.\n      </description>\n      <arg name=\"format\" type=\"uint\" enum=\"wl_shm.format\" summary=\"buffer format\"/>\n      <arg name=\"width\" type=\"uint\" summary=\"buffer width\"/>\n      <arg name=\"height\" type=\"uint\" summary=\"buffer height\"/>\n      <arg name=\"stride\" type=\"uint\" summary=\"buffer stride\"/>\n    </event>\n\n    <request name=\"copy\">\n      <description summary=\"copy the frame\">\n        Copy the frame to the supplied buffer. The buffer must have a the\n        correct size, see zwlr_screencopy_frame_v1.buffer and\n        zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a\n        supported format.\n\n        If the frame is successfully copied, a \"flags\" and a \"ready\" events are\n        sent. Otherwise, a \"failed\" event is sent.\n      </description>\n      <arg name=\"buffer\" type=\"object\" interface=\"wl_buffer\"/>\n    </request>\n\n    <enum name=\"error\">\n      <entry name=\"already_used\" value=\"0\"\n        summary=\"the object has already been used to copy a wl_buffer\"/>\n      <entry name=\"invalid_buffer\" value=\"1\"\n        summary=\"buffer attributes are invalid\"/>\n    </enum>\n\n    <enum name=\"flags\" bitfield=\"true\">\n      <entry name=\"y_invert\" value=\"1\" summary=\"contents are y-inverted\"/>\n    </enum>\n\n    <event name=\"flags\">\n      <description summary=\"frame flags\">\n        Provides flags about the frame. This event is sent once before the\n        \"ready\" event.\n      </description>\n      <arg name=\"flags\" type=\"uint\" enum=\"flags\" summary=\"frame flags\"/>\n    </event>\n\n    <event name=\"ready\">\n      <description summary=\"indicates frame is available for reading\">\n        Called as soon as the frame is copied, indicating it is available\n        for reading. This event includes the time at which presentation happened\n        at.\n\n        The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,\n        each component being an unsigned 32-bit value. Whole seconds are in\n        tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,\n        and the additional fractional part in tv_nsec as nanoseconds. Hence,\n        for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part\n        may have an arbitrary offset at start.\n\n        After receiving this event, the client should destroy the object.\n      </description>\n      <arg name=\"tv_sec_hi\" type=\"uint\"\n           summary=\"high 32 bits of the seconds part of the timestamp\"/>\n      <arg name=\"tv_sec_lo\" type=\"uint\"\n           summary=\"low 32 bits of the seconds part of the timestamp\"/>\n      <arg name=\"tv_nsec\" type=\"uint\"\n           summary=\"nanoseconds part of the timestamp\"/>\n    </event>\n\n    <event name=\"failed\">\n      <description summary=\"frame copy failed\">\n        This event indicates that the attempted frame copy has failed.\n\n        After receiving this event, the client should destroy the object.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"delete this object, used or not\">\n        Destroys the frame. This request can be sent at any time by the client.\n      </description>\n    </request>\n\n    <!-- Version 2 additions -->\n    <request name=\"copy_with_damage\" since=\"2\">\n      <description summary=\"copy the frame when it's damaged\">\n        Same as copy, except it waits until there is damage to copy.\n      </description>\n      <arg name=\"buffer\" type=\"object\" interface=\"wl_buffer\"/>\n    </request>\n\n    <event name=\"damage\" since=\"2\">\n      <description summary=\"carries the coordinates of the damaged region\">\n        This event is sent right before the ready event when copy_with_damage is\n        requested. It may be generated multiple times for each copy_with_damage\n        request.\n\n        The arguments describe a box around an area that has changed since the\n        last copy request that was derived from the current screencopy manager\n        instance.\n\n        The union of all regions received between the call to copy_with_damage\n        and a ready event is the total damage since the prior ready event.\n      </description>\n      <arg name=\"x\" type=\"uint\" summary=\"damaged x coordinates\"/>\n      <arg name=\"y\" type=\"uint\" summary=\"damaged y coordinates\"/>\n      <arg name=\"width\" type=\"uint\" summary=\"current width\"/>\n      <arg name=\"height\" type=\"uint\" summary=\"current height\"/>\n    </event>\n\n    <!-- Version 3 additions -->\n    <event name=\"linux_dmabuf\" since=\"3\">\n      <description summary=\"linux-dmabuf buffer information\">\n        Provides information about linux-dmabuf buffer parameters that need to\n        be used for this frame. This event is sent once after the frame is\n        created if linux-dmabuf buffers are supported.\n      </description>\n      <arg name=\"format\" type=\"uint\" summary=\"fourcc pixel format\"/>\n      <arg name=\"width\" type=\"uint\" summary=\"buffer width\"/>\n      <arg name=\"height\" type=\"uint\" summary=\"buffer height\"/>\n    </event>\n\n    <event name=\"buffer_done\" since=\"3\">\n      <description summary=\"all buffer types reported\">\n        This event is sent once after all buffer events have been sent.\n\n        The client should proceed to create a buffer of one of the supported\n        types, and send a \"copy\" request.\n      </description>\n    </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocols/wlr-virtual-pointer-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_virtual_pointer_unstable_v1\">\n  <copyright>\n    Copyright © 2019 Josef Gajdusek\n\n    Permission is hereby granted, free of charge, to any person obtaining a\n    copy of this software and associated documentation files (the \"Software\"),\n    to deal in the Software without restriction, including without limitation\n    the rights to use, copy, modify, merge, publish, distribute, sublicense,\n    and/or sell copies of the Software, and to permit persons to whom the\n    Software is furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice (including the next\n    paragraph) shall be included in all copies or substantial portions of the\n    Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n    DEALINGS IN THE SOFTWARE.\n  </copyright>\n\n  <interface name=\"zwlr_virtual_pointer_v1\" version=\"2\">\n    <description summary=\"virtual pointer\">\n      This protocol allows clients to emulate a physical pointer device. The\n      requests are mostly mirror opposites of those specified in wl_pointer.\n    </description>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_axis\" value=\"0\"\n        summary=\"client sent invalid axis enumeration value\" />\n      <entry name=\"invalid_axis_source\" value=\"1\"\n        summary=\"client sent invalid axis source enumeration value\" />\n    </enum>\n\n    <request name=\"motion\">\n      <description summary=\"pointer relative motion event\">\n        The pointer has moved by a relative amount to the previous request.\n\n        Values are in the global compositor space.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"dx\" type=\"fixed\" summary=\"displacement on the x-axis\"/>\n      <arg name=\"dy\" type=\"fixed\" summary=\"displacement on the y-axis\"/>\n    </request>\n\n    <request name=\"motion_absolute\">\n      <description summary=\"pointer absolute motion event\">\n        The pointer has moved in an absolute coordinate frame.\n\n        Value of x can range from 0 to x_extent, value of y can range from 0\n        to y_extent.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"x\" type=\"uint\" summary=\"position on the x-axis\"/>\n      <arg name=\"y\" type=\"uint\" summary=\"position on the y-axis\"/>\n      <arg name=\"x_extent\" type=\"uint\" summary=\"extent of the x-axis\"/>\n      <arg name=\"y_extent\" type=\"uint\" summary=\"extent of the y-axis\"/>\n    </request>\n\n    <request name=\"button\">\n      <description summary=\"button event\">\n        A button was pressed or released.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"button\" type=\"uint\" summary=\"button that produced the event\"/>\n      <arg name=\"state\" type=\"uint\" enum=\"wl_pointer.button_state\" summary=\"physical state of the button\"/>\n    </request>\n\n    <request name=\"axis\">\n      <description summary=\"axis event\">\n        Scroll and other axis requests.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"axis\" type=\"uint\" enum=\"wl_pointer.axis\" summary=\"axis type\"/>\n      <arg name=\"value\" type=\"fixed\" summary=\"length of vector in touchpad coordinates\"/>\n    </request>\n\n    <request name=\"frame\">\n      <description summary=\"end of a pointer event sequence\">\n        Indicates the set of events that logically belong together.\n      </description>\n    </request>\n\n    <request name=\"axis_source\">\n      <description summary=\"axis source event\">\n        Source information for scroll and other axis.\n      </description>\n      <arg name=\"axis_source\" type=\"uint\" enum=\"wl_pointer.axis_source\" summary=\"source of the axis event\"/>\n    </request>\n\n    <request name=\"axis_stop\">\n      <description summary=\"axis stop event\">\n        Stop notification for scroll and other axes.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"axis\" type=\"uint\" enum=\"wl_pointer.axis\" summary=\"the axis stopped with this event\"/>\n    </request>\n\n    <request name=\"axis_discrete\">\n      <description summary=\"axis click event\">\n        Discrete step information for scroll and other axes.\n\n        This event allows the client to extend data normally sent using the axis\n        event with discrete value.\n      </description>\n      <arg name=\"time\" type=\"uint\" summary=\"timestamp with millisecond granularity\"/>\n      <arg name=\"axis\" type=\"uint\" enum=\"wl_pointer.axis\" summary=\"axis type\"/>\n      <arg name=\"value\" type=\"fixed\" summary=\"length of vector in touchpad coordinates\"/>\n      <arg name=\"discrete\" type=\"int\" summary=\"number of steps\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\" since=\"1\">\n      <description summary=\"destroy the virtual pointer object\"/>\n    </request>\n  </interface>\n\n  <interface name=\"zwlr_virtual_pointer_manager_v1\" version=\"2\">\n    <description summary=\"virtual pointer manager\">\n      This object allows clients to create individual virtual pointer objects.\n    </description>\n\n    <request name=\"create_virtual_pointer\">\n      <description summary=\"Create a new virtual pointer\">\n        Creates a new virtual pointer. The optional seat is a suggestion to the\n        compositor.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\" allow-null=\"true\"/>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_virtual_pointer_v1\"/>\n    </request>\n\n    <request name=\"destroy\" type=\"destructor\" since=\"1\">\n      <description summary=\"destroy the virtual pointer manager\"/>\n    </request>\n\n    <!-- Version 2 additions -->\n    <request name=\"create_virtual_pointer_with_output\" since=\"2\">\n      <description summary=\"Create a new virtual pointer\">\n        Creates a new virtual pointer. The seat and the output arguments are\n        optional. If the seat argument is set, the compositor should assign the\n        input device to the requested seat. If the output argument is set, the\n        compositor should map the input device to the requested output.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\" allow-null=\"true\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\" allow-null=\"true\"/>\n      <arg name=\"id\" type=\"new_id\" interface=\"zwlr_virtual_pointer_v1\"/>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "scripts/generateShaderIncludes.sh",
    "content": "#!/bin/sh\n\nSHADERS_SRC=\"./src/render/shaders/glsl\"\n\necho \"-- Generating shader includes\"\n\nif [ ! -d ./src/render/shaders ]; then\n\tmkdir ./src/render/shaders\nfi\n\necho '#pragma once' > ./src/render/shaders/Shaders.hpp\necho '#include <map>' >> ./src/render/shaders/Shaders.hpp\necho 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/render/shaders/Shaders.hpp\n\nfor filename in `ls ${SHADERS_SRC}`; do\n\techo \"--\t${filename}\"\n\t\n\t{ echo -n 'R\"#('; cat ${SHADERS_SRC}/${filename}; echo ')#\"'; } > ./src/render/shaders/${filename}.inc\n\techo \"{\\\"${filename}\\\",\" >> ./src/render/shaders/Shaders.hpp\n\techo \"#include \\\"./${filename}.inc\\\"\" >> ./src/render/shaders/Shaders.hpp\n\techo \"},\" >> ./src/render/shaders/Shaders.hpp\ndone\n\necho '};' >> ./src/render/shaders/Shaders.hpp\n"
  },
  {
    "path": "scripts/hyprlandStaticAsan.diff",
    "content": "diff --git a/CMakeLists.txt b/CMakeLists.txt\nindex f26a5c3c..3dfef333 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -143,6 +143,8 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)\n     message(STATUS \"Enabling ASan\")\n \n     target_link_libraries(Hyprland asan)\n+    pkg_check_modules(ffidep REQUIRED IMPORTED_TARGET libffi)\n+    target_link_libraries(Hyprland ${CMAKE_SOURCE_DIR}/libwayland-server.a PkgConfig::ffidep)\n     target_compile_options(Hyprland PUBLIC -fsanitize=address)\n   endif()\n \n"
  },
  {
    "path": "scripts/waylandStatic.diff",
    "content": "diff --git a/src/meson.build b/src/meson.build\nindex 5d04334..6645eec 100644\n--- a/src/meson.build\n+++ b/src/meson.build\n@@ -170,7 +170,7 @@ if get_option('libraries')\n \t\terror('We probably need to bump the SONAME of libwayland-server and -client')\n \tendif\n \n-\twayland_server = library(\n+\twayland_server = static_library(\n \t\t'wayland-server',\n \t\tsources: [\n \t\t\twayland_server_protocol_core_h,\n@@ -180,9 +180,6 @@ if get_option('libraries')\n \t\t\t'wayland-shm.c',\n \t\t\t'event-loop.c'\n \t\t],\n-\t\t# To avoid an unnecessary SONAME bump, wayland 1.x.y produces\n-\t\t# libwayland-server.so.0.x.y.\n-\t\tversion: '.'.join(['0', wayland_version[1], wayland_version[2]]),\n \t\tdependencies: [\n \t\t\tepoll_dep,\n \t\t\tffi_dep,\n"
  },
  {
    "path": "src/Compositor.cpp",
    "content": "#include <ranges>\n#include <re2/re2.h>\n\n#include \"Compositor.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"desktop/DesktopTypes.hpp\"\n#include \"desktop/state/FocusState.hpp\"\n#include \"desktop/history/WindowHistoryTracker.hpp\"\n#include \"desktop/history/WorkspaceHistoryTracker.hpp\"\n#include \"desktop/view/Group.hpp\"\n#include \"helpers/Splashes.hpp\"\n#include \"config/ConfigValue.hpp\"\n#include \"config/ConfigWatcher.hpp\"\n#include \"managers/CursorManager.hpp\"\n#include \"managers/TokenManager.hpp\"\n#include \"managers/PointerManager.hpp\"\n#include \"managers/SeatManager.hpp\"\n#include \"managers/VersionKeeperManager.hpp\"\n#include \"managers/DonationNagManager.hpp\"\n#include \"managers/ANRManager.hpp\"\n#include \"managers/eventLoop/EventLoopManager.hpp\"\n#include \"managers/permissions/DynamicPermissionManager.hpp\"\n#include \"managers/screenshare/ScreenshareManager.hpp\"\n#include <algorithm>\n#include <aquamarine/output/Output.hpp>\n#include <bit>\n#include <ctime>\n#include <random>\n#include <print>\n#include <cstring>\n#include <filesystem>\n#include <unordered_set>\n#include \"debug/HyprCtl.hpp\"\n#include \"debug/crash/CrashReporter.hpp\"\n#include \"render/GLRenderer.hpp\"\n#include \"render/ShaderLoader.hpp\"\n#ifdef USES_SYSTEMD\n#include <helpers/SdDaemon.hpp> // for SdNotify\n#endif\n#include \"helpers/fs/FsUtils.hpp\"\n#include \"helpers/env/Env.hpp\"\n#include \"protocols/FractionalScale.hpp\"\n#include \"protocols/PointerConstraints.hpp\"\n#include \"protocols/LayerShell.hpp\"\n#include \"protocols/XDGShell.hpp\"\n#include \"protocols/XDGOutput.hpp\"\n#include \"protocols/SecurityContext.hpp\"\n#include \"protocols/ColorManagement.hpp\"\n#include \"protocols/core/Compositor.hpp\"\n#include \"protocols/core/Subcompositor.hpp\"\n#include \"desktop/view/LayerSurface.hpp\"\n#include \"layout/space/Space.hpp\"\n#include \"render/Renderer.hpp\"\n#include \"xwayland/XWayland.hpp\"\n#include \"helpers/ByteOperations.hpp\"\n#include \"render/decorations/CHyprGroupBarDecoration.hpp\"\n\n#include \"managers/KeybindManager.hpp\"\n#include \"managers/SessionLockManager.hpp\"\n#include \"managers/XWaylandManager.hpp\"\n\n#include \"config/ConfigManager.hpp\"\n#include \"render/OpenGL.hpp\"\n#include \"managers/input/InputManager.hpp\"\n#include \"managers/animation/AnimationManager.hpp\"\n#include \"managers/animation/DesktopAnimationManager.hpp\"\n#include \"managers/EventManager.hpp\"\n#include \"managers/ProtocolManager.hpp\"\n#include \"managers/WelcomeManager.hpp\"\n#include \"render/AsyncResourceGatherer.hpp\"\n#include \"plugins/PluginSystem.hpp\"\n#include \"hyprerror/HyprError.hpp\"\n#include \"debug/HyprNotificationOverlay.hpp\"\n#include \"debug/HyprDebugOverlay.hpp\"\n#include \"helpers/MonitorFrameScheduler.hpp\"\n#include \"i18n/Engine.hpp\"\n#include \"layout/LayoutManager.hpp\"\n#include \"layout/target/WindowTarget.hpp\"\n#include \"event/EventBus.hpp\"\n\n#include <hyprutils/string/String.hpp>\n#include <aquamarine/input/Input.hpp>\n\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/resource.h>\n#include <malloc.h>\n#include <unistd.h>\n#include <xf86drm.h>\n\nusing namespace Hyprutils::String;\nusing namespace Aquamarine;\nusing enum NContentType::eContentType;\nusing namespace NColorManagement;\n\nstatic int handleCritSignal(int signo, void* data) {\n    Log::logger->log(Log::DEBUG, \"Hyprland received signal {}\", signo);\n\n    if (signo == SIGTERM || signo == SIGINT || signo == SIGKILL)\n        g_pCompositor->stopCompositor();\n\n    return 0;\n}\n\nstatic void handleUnrecoverableSignal(int sig) {\n\n    // remove our handlers\n    signal(SIGABRT, SIG_DFL);\n    signal(SIGSEGV, SIG_DFL);\n\n    // Kill the program if the crash-reporter is caught in a deadlock.\n    signal(SIGALRM, [](int _) {\n        char const* msg = \"\\nCrashReporter exceeded timeout, forcefully exiting\\n\";\n        write(2, msg, strlen(msg));\n        abort();\n    });\n    alarm(15);\n\n    CrashReporter::createAndSaveCrash(sig);\n\n    abort();\n}\n\nstatic void handleUserSignal(int sig) {\n    if (sig == SIGUSR1) {\n        // means we have to unwind a timed out event\n        throw std::exception();\n    }\n}\n\nbool CCompositor::setWatchdogFd(int fd) {\n    m_watchdogWriteFd = Hyprutils::OS::CFileDescriptor{fd};\n    return m_watchdogWriteFd.isValid() && !m_watchdogWriteFd.isClosed();\n}\n\nvoid CCompositor::bumpNofile() {\n    if (!getrlimit(RLIMIT_NOFILE, &m_originalNofile))\n        Log::logger->log(Log::DEBUG, \"Old rlimit: soft -> {}, hard -> {}\", m_originalNofile.rlim_cur, m_originalNofile.rlim_max);\n    else {\n        Log::logger->log(Log::ERR, \"Failed to get NOFILE rlimits\");\n        m_originalNofile.rlim_max = 0;\n        return;\n    }\n\n    rlimit newLimit = m_originalNofile;\n\n    newLimit.rlim_cur = newLimit.rlim_max;\n\n    if (setrlimit(RLIMIT_NOFILE, &newLimit) < 0) {\n        Log::logger->log(Log::ERR, \"Failed bumping NOFILE limits higher\");\n        m_originalNofile.rlim_max = 0;\n        return;\n    }\n\n    if (!getrlimit(RLIMIT_NOFILE, &newLimit))\n        Log::logger->log(Log::DEBUG, \"New rlimit: soft -> {}, hard -> {}\", newLimit.rlim_cur, newLimit.rlim_max);\n}\n\nvoid CCompositor::restoreNofile() {\n    if (m_originalNofile.rlim_max <= 0)\n        return;\n\n    if (setrlimit(RLIMIT_NOFILE, &m_originalNofile) < 0)\n        Log::logger->log(Log::ERR, \"Failed restoring NOFILE limits\");\n}\n\nbool CCompositor::supportsDrmSyncobjTimeline() const {\n    return m_drm.syncobjSupport || m_drmRenderNode.syncObjSupport;\n}\n\nvoid CCompositor::setMallocThreshold() {\n#ifdef M_TRIM_THRESHOLD\n    // The default is 128 pages,\n    // which is very large and can lead to a lot of memory used for no reason\n    // because trimming hasn't happened\n    static const int PAGESIZE = sysconf(_SC_PAGESIZE);\n    mallopt(M_TRIM_THRESHOLD, 6 * PAGESIZE);\n#endif\n}\n\nCCompositor::CCompositor(bool onlyConfig) : m_onlyConfigVerification(onlyConfig), m_hyprlandPID(getpid()) {\n    if (onlyConfig)\n        return;\n\n    setMallocThreshold();\n\n    m_hyprTempDataRoot = std::string{getenv(\"XDG_RUNTIME_DIR\")} + \"/hypr\";\n\n    if (m_hyprTempDataRoot.starts_with(\"/hypr\")) {\n        std::println(\"Bailing out, $XDG_RUNTIME_DIR is invalid\");\n        throw std::runtime_error(\"CCompositor() failed\");\n    }\n\n    if (!m_hyprTempDataRoot.starts_with(\"/run/user\"))\n        std::println(\"[!!WARNING!!] XDG_RUNTIME_DIR looks non-standard. Proceeding anyways...\");\n\n    std::random_device              dev;\n    std::mt19937                    engine(dev());\n    std::uniform_int_distribution<> distribution(0, INT32_MAX);\n\n    m_instanceSignature = std::format(\"{}_{}_{}\", GIT_COMMIT_HASH, std::time(nullptr), distribution(engine));\n\n    setenv(\"HYPRLAND_INSTANCE_SIGNATURE\", m_instanceSignature.c_str(), true);\n\n    if (!std::filesystem::exists(m_hyprTempDataRoot))\n        mkdir(m_hyprTempDataRoot.c_str(), S_IRWXU);\n    else if (!std::filesystem::is_directory(m_hyprTempDataRoot)) {\n        std::println(\"Bailing out, {} is not a directory\", m_hyprTempDataRoot);\n        throw std::runtime_error(\"CCompositor() failed\");\n    }\n\n    m_instancePath = m_hyprTempDataRoot + \"/\" + m_instanceSignature;\n\n    if (std::filesystem::exists(m_instancePath)) {\n        std::println(\"Bailing out, {} exists??\", m_instancePath);\n        throw std::runtime_error(\"CCompositor() failed\");\n    }\n\n    if (mkdir(m_instancePath.c_str(), S_IRWXU) < 0) {\n        std::println(\"Bailing out, couldn't create {}\", m_instancePath);\n        throw std::runtime_error(\"CCompositor() failed\");\n    }\n\n    Log::logger->initIS(m_instancePath);\n\n    Log::logger->log(Log::DEBUG, \"Instance Signature: {}\", m_instanceSignature);\n\n    Log::logger->log(Log::DEBUG, \"Runtime directory: {}\", m_instancePath);\n\n    Log::logger->log(Log::DEBUG, \"Hyprland PID: {}\", m_hyprlandPID);\n\n    Log::logger->log(Log::DEBUG, \"===== SYSTEM INFO: =====\");\n\n    logSystemInfo();\n\n    Log::logger->log(Log::DEBUG, \"========================\");\n\n    Log::logger->log(Log::DEBUG, \"\\n\\n\"); // pad\n\n    Log::logger->log(Log::INFO, \"If you are crashing, or encounter any bugs, please consult https://wiki.hypr.land/Crashes-and-Bugs/\\n\\n\");\n\n    setRandomSplash();\n\n    Log::logger->log(Log::DEBUG, \"\\nCurrent splash: {}\\n\\n\", m_currentSplash);\n\n    bumpNofile();\n}\n\nCCompositor::~CCompositor() {\n    if (!m_isShuttingDown && !m_onlyConfigVerification)\n        cleanup();\n}\n\nvoid CCompositor::setRandomSplash() {\n    auto        tt    = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n    auto        local = *localtime(&tt);\n\n    const auto* SPLASHES = &NSplashes::SPLASHES;\n\n    if (local.tm_mon + 1 == 12 && local.tm_mday >= 23 && local.tm_mday <= 27) // dec 23-27\n        SPLASHES = &NSplashes::SPLASHES_CHRISTMAS;\n    if ((local.tm_mon + 1 == 12 && local.tm_mday >= 29) || (local.tm_mon + 1 == 1 && local.tm_mday <= 3))\n        SPLASHES = &NSplashes::SPLASHES_NEWYEAR;\n\n    std::random_device              dev;\n    std::mt19937                    engine(dev());\n    std::uniform_int_distribution<> distribution(0, SPLASHES->size() - 1);\n\n    m_currentSplash = SPLASHES->at(distribution(engine));\n}\n\nstatic std::vector<SP<Aquamarine::IOutput>> pendingOutputs;\n\n//\n\nstatic bool filterGlobals(const wl_client* client, const wl_global* global, void* data) {\n    if (!PROTO::securityContext->isClientSandboxed(client))\n        return true;\n\n    return !g_pProtocolManager || !g_pProtocolManager->isGlobalPrivileged(global);\n}\n\n//\nvoid CCompositor::initServer(std::string socketName, int socketFd) {\n    if (m_onlyConfigVerification) {\n        g_pKeybindManager   = makeUnique<CKeybindManager>();\n        g_pAnimationManager = makeUnique<CHyprAnimationManager>();\n        g_pConfigManager    = makeUnique<CConfigManager>();\n\n        std::println(\"\\n\\n======== Config parsing result:\\n\\n{}\", g_pConfigManager->verify());\n        return;\n    }\n\n    m_wlDisplay = wl_display_create();\n\n    wl_display_set_global_filter(m_wlDisplay, ::filterGlobals, nullptr);\n\n    m_wlEventLoop = wl_display_get_event_loop(m_wlDisplay);\n\n    // register crit signal handler\n    m_critSigSource = wl_event_loop_add_signal(m_wlEventLoop, SIGTERM, handleCritSignal, nullptr);\n\n    if (!Env::envEnabled(\"HYPRLAND_NO_CRASHREPORTER\")) {\n        signal(SIGSEGV, handleUnrecoverableSignal);\n        signal(SIGABRT, handleUnrecoverableSignal);\n    }\n    signal(SIGUSR1, handleUserSignal);\n\n    initManagers(STAGE_PRIORITY);\n\n    Log::logger->initCallbacks();\n\n    // set the buffer size to 1MB to avoid disconnects due to an app hanging for a short while\n    wl_display_set_default_max_buffer_size(m_wlDisplay, 1_MB);\n\n    Aquamarine::SBackendOptions           options{};\n    SP<Hyprutils::CLI::CLoggerConnection> conn = makeShared<Hyprutils::CLI::CLoggerConnection>(Log::logger->hu());\n    conn->setLogLevel(Log::DEBUG);\n    conn->setName(\"aquamarine\");\n    options.logConnection = std::move(conn);\n\n    std::vector<Aquamarine::SBackendImplementationOptions> implementations;\n    Aquamarine::SBackendImplementationOptions              option;\n    option.backendType        = Aquamarine::eBackendType::AQ_BACKEND_HEADLESS;\n    option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_MANDATORY;\n    implementations.emplace_back(option);\n    option.backendType        = Aquamarine::eBackendType::AQ_BACKEND_DRM;\n    option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_IF_AVAILABLE;\n    implementations.emplace_back(option);\n    option.backendType        = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND;\n    option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_FALLBACK;\n    implementations.emplace_back(option);\n\n    m_aqBackend = CBackend::create(implementations, options);\n\n    if (!m_aqBackend) {\n        Log::logger->log(\n            Log::CRIT,\n            \"m_pAqBackend was null! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a Wayland \"\n            \"session, NOT an X11 one.\");\n        throwError(\"CBackend::create() failed!\");\n    }\n\n    // TODO: headless only\n\n    initAllSignals();\n\n    if (!m_aqBackend->start()) {\n        Log::logger->log(\n            Log::CRIT,\n            \"m_pAqBackend couldn't start! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a \"\n            \"Wayland session, NOT an X11 one.\");\n        throwError(\"CBackend::create() failed!\");\n    }\n\n    m_initialized = true;\n\n    m_drm.fd = m_aqBackend->drmFD();\n    Log::logger->log(Log::DEBUG, \"Running on DRMFD: {}\", m_drm.fd);\n\n    m_drmRenderNode.fd = m_aqBackend->drmRenderNodeFD();\n    Log::logger->log(Log::DEBUG, \"Using RENDERNODEFD: {}\", m_drmRenderNode.fd);\n\n#if defined(__linux__)\n    auto syncObjSupport = [](auto fd) {\n        if (fd < 0)\n            return false;\n\n        uint64_t cap = 0;\n        int      ret = drmGetCap(fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap);\n        return ret == 0 && cap != 0;\n    };\n\n    m_drm.syncobjSupport = syncObjSupport(m_drm.fd);\n    Log::logger->log(Log::DEBUG, \"DRM DisplayNode syncobj timeline support: {}\", m_drm.syncobjSupport ? \"yes\" : \"no\");\n\n    m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd);\n    Log::logger->log(Log::DEBUG, \"DRM RenderNode syncobj timeline support: {}\", m_drmRenderNode.syncObjSupport ? \"yes\" : \"no\");\n\n    if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport)\n        Log::logger->log(Log::DEBUG, \"DRM no syncobj support, disabling explicit sync\");\n#else\n    Log::logger->log(Log::DEBUG, \"DRM syncobj timeline support: no (not linux)\");\n#endif\n\n    if (!socketName.empty() && socketFd != -1) {\n        fcntl(socketFd, F_SETFD, FD_CLOEXEC);\n        const auto RETVAL = wl_display_add_socket_fd(m_wlDisplay, socketFd);\n        if (RETVAL >= 0) {\n            m_wlDisplaySocket = socketName;\n            Log::logger->log(Log::DEBUG, \"wl_display_add_socket_fd for {} succeeded with {}\", socketName, RETVAL);\n        } else\n            Log::logger->log(Log::WARN, \"wl_display_add_socket_fd for {} returned {}: skipping\", socketName, RETVAL);\n    } else {\n        // get socket, avoid using 0\n        for (int candidate = 1; candidate <= 32; candidate++) {\n            const auto CANDIDATESTR = (\"wayland-\" + std::to_string(candidate));\n            const auto RETVAL       = wl_display_add_socket(m_wlDisplay, CANDIDATESTR.c_str());\n            if (RETVAL >= 0) {\n                m_wlDisplaySocket = CANDIDATESTR;\n                Log::logger->log(Log::DEBUG, \"wl_display_add_socket for {} succeeded with {}\", CANDIDATESTR, RETVAL);\n                break;\n            } else\n                Log::logger->log(Log::WARN, \"wl_display_add_socket for {} returned {}: skipping candidate {}\", CANDIDATESTR, RETVAL, candidate);\n        }\n    }\n\n    if (m_wlDisplaySocket.empty()) {\n        Log::logger->log(Log::WARN, \"All candidates failed, trying wl_display_add_socket_auto\");\n        const auto SOCKETSTR = wl_display_add_socket_auto(m_wlDisplay);\n        if (SOCKETSTR)\n            m_wlDisplaySocket = SOCKETSTR;\n    }\n\n    if (m_wlDisplaySocket.empty()) {\n        Log::logger->log(Log::CRIT, \"m_szWLDisplaySocket NULL!\");\n        throwError(\"m_szWLDisplaySocket was null! (wl_display_add_socket and wl_display_add_socket_auto failed)\");\n    }\n\n    setenv(\"WAYLAND_DISPLAY\", m_wlDisplaySocket.c_str(), 1);\n    if (!getenv(\"XDG_CURRENT_DESKTOP\")) {\n        setenv(\"XDG_CURRENT_DESKTOP\", \"Hyprland\", 1);\n        m_desktopEnvSet = true;\n    }\n\n    initManagers(STAGE_BASICINIT);\n\n    initManagers(STAGE_LATE);\n\n    for (auto const& o : pendingOutputs) {\n        onNewMonitor(o);\n    }\n    pendingOutputs.clear();\n}\n\nvoid CCompositor::initAllSignals() {\n    m_aqBackend->events.newOutput.listenStatic([this](const SP<Aquamarine::IOutput>& output) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine output with name {}\", output->name);\n        if (m_initialized)\n            onNewMonitor(output);\n        else\n            pendingOutputs.emplace_back(output);\n    });\n\n    m_aqBackend->events.newPointer.listenStatic([](const SP<Aquamarine::IPointer>& dev) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine pointer with name {}\", dev->getName());\n        g_pInputManager->newMouse(dev);\n        g_pInputManager->updateCapabilities();\n    });\n\n    m_aqBackend->events.newKeyboard.listenStatic([](const SP<Aquamarine::IKeyboard>& dev) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine keyboard with name {}\", dev->getName());\n        g_pInputManager->newKeyboard(dev);\n        g_pInputManager->updateCapabilities();\n    });\n\n    m_aqBackend->events.newTouch.listenStatic([](const SP<Aquamarine::ITouch>& dev) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine touch with name {}\", dev->getName());\n        g_pInputManager->newTouchDevice(dev);\n        g_pInputManager->updateCapabilities();\n    });\n\n    m_aqBackend->events.newSwitch.listenStatic([](const SP<Aquamarine::ISwitch>& dev) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine switch with name {}\", dev->getName());\n        g_pInputManager->newSwitch(dev);\n    });\n\n    m_aqBackend->events.newTablet.listenStatic([](const SP<Aquamarine::ITablet>& dev) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine tablet with name {}\", dev->getName());\n        g_pInputManager->newTablet(dev);\n    });\n\n    m_aqBackend->events.newTabletPad.listenStatic([](const SP<Aquamarine::ITabletPad>& dev) {\n        Log::logger->log(Log::DEBUG, \"New aquamarine tablet pad with name {}\", dev->getName());\n        g_pInputManager->newTabletPad(dev);\n    });\n\n    if (m_aqBackend->hasSession()) {\n        m_aqBackend->session->events.changeActive.listenStatic([this] {\n            if (m_aqBackend->session->active) {\n                Log::logger->log(Log::DEBUG, \"Session got activated!\");\n\n                m_sessionActive = true;\n\n                // Reset animation tick state to avoid stale timer issues after suspend/wake\n                if (g_pAnimationManager)\n                    g_pAnimationManager->resetTickState();\n\n                for (auto const& m : m_monitors) {\n                    scheduleFrameForMonitor(m);\n                    m->applyMonitorRule(&m->m_activeMonitorRule, true);\n                }\n\n                g_pConfigManager->m_wantsMonitorReload = true;\n                g_pCursorManager->syncGsettings();\n            } else {\n                Log::logger->log(Log::DEBUG, \"Session got deactivated!\");\n\n                m_sessionActive = false;\n            }\n        });\n    }\n}\n\nvoid CCompositor::removeAllSignals() {\n    ;\n}\n\nvoid CCompositor::cleanEnvironment() {\n    // in compositor constructor\n    unsetenv(\"WAYLAND_DISPLAY\");\n    // in startCompositor\n    unsetenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n    // in main\n    unsetenv(\"HYPRLAND_CMD\");\n    unsetenv(\"XDG_BACKEND\");\n    if (m_desktopEnvSet)\n        unsetenv(\"XDG_CURRENT_DESKTOP\");\n\n    if (m_aqBackend->hasSession() && !Env::envEnabled(\"HYPRLAND_NO_SD_VARS\")) {\n        const auto CMD =\n#ifdef USES_SYSTEMD\n            \"systemctl --user unset-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash \"\n            \"dbus-update-activation-environment 2>/dev/null && \"\n#endif\n            \"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS\";\n        CKeybindManager::spawn(CMD);\n    }\n}\n\nvoid CCompositor::stopCompositor() {\n    Log::logger->log(Log::DEBUG, \"Hyprland is stopping!\");\n\n    // this stops the wayland loop, wl_display_run\n    wl_display_terminate(m_wlDisplay);\n    m_isShuttingDown = true;\n}\n\nvoid CCompositor::cleanup() {\n    if (!m_wlDisplay)\n        return;\n\n    if (m_watchdogWriteFd.isValid())\n        write(m_watchdogWriteFd.get(), \"end\", 3);\n\n    signal(SIGABRT, SIG_DFL);\n    signal(SIGSEGV, SIG_DFL);\n\n    removeLockFile();\n\n    m_isShuttingDown = true;\n\n#ifdef USES_SYSTEMD\n    if (NSystemd::sdBooted() > 0 && !Env::envEnabled(\"HYPRLAND_NO_SD_NOTIFY\"))\n        NSystemd::sdNotify(0, \"STOPPING=1\");\n#endif\n\n    cleanEnvironment();\n\n    // unload all remaining plugins while the compositor is\n    // still in a normal working state.\n    g_pPluginSystem->unloadAllPlugins();\n\n    m_workspaces.clear();\n    m_windows.clear();\n\n    for (auto const& m : m_monitors) {\n        g_pHyprOpenGL->destroyMonitorResources(m);\n    }\n\n    g_pXWayland.reset();\n\n    m_monitors.clear();\n\n    wl_display_destroy_clients(g_pCompositor->m_wlDisplay);\n    removeAllSignals();\n\n    g_pInputManager.reset();\n    g_pDynamicPermissionManager.reset();\n    g_pDecorationPositioner.reset();\n    g_pCursorManager.reset();\n    g_pPluginSystem.reset();\n    g_pHyprNotificationOverlay.reset();\n    g_pDebugOverlay.reset();\n    g_pEventManager.reset();\n    g_pSessionLockManager.reset();\n    g_pProtocolManager.reset();\n    g_pHyprRenderer.reset();\n    g_pHyprOpenGL.reset();\n    Render::g_pShaderLoader.reset();\n    g_pConfigManager.reset();\n    g_layoutManager.reset();\n    g_pHyprError.reset();\n    g_pConfigManager.reset();\n    g_pKeybindManager.reset();\n    g_pXWaylandManager.reset();\n    g_pPointerManager.reset();\n    g_pSeatManager.reset();\n    g_pHyprCtl.reset();\n    g_pEventLoopManager.reset();\n    g_pVersionKeeperMgr.reset();\n    g_pDonationNagManager.reset();\n    g_pWelcomeManager.reset();\n    g_pANRManager.reset();\n    g_pConfigWatcher.reset();\n    g_pAsyncResourceGatherer.reset();\n\n    if (m_aqBackend)\n        m_aqBackend.reset();\n\n    if (m_critSigSource)\n        wl_event_source_remove(m_critSigSource);\n\n    // this frees all wayland resources, including sockets\n    wl_display_destroy(m_wlDisplay);\n}\n\nvoid CCompositor::initManagers(eManagersInitStage stage) {\n    switch (stage) {\n        case STAGE_PRIORITY: {\n            Log::logger->log(Log::DEBUG, \"Creating the EventLoopManager!\");\n            g_pEventLoopManager = makeUnique<CEventLoopManager>(m_wlDisplay, m_wlEventLoop);\n\n            Log::logger->log(Log::DEBUG, \"Creating the KeybindManager!\");\n            g_pKeybindManager = makeUnique<CKeybindManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the AnimationManager!\");\n            g_pAnimationManager = makeUnique<CHyprAnimationManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the DynamicPermissionManager!\");\n            g_pDynamicPermissionManager = makeUnique<CDynamicPermissionManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the ConfigManager!\");\n            g_pConfigManager = makeUnique<CConfigManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the CHyprError!\");\n            g_pHyprError = makeUnique<CHyprError>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the LayoutManager!\");\n            g_layoutManager = makeUnique<Layout::CLayoutManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the TokenManager!\");\n            g_pTokenManager = makeUnique<CTokenManager>();\n\n            g_pConfigManager->init();\n\n            Log::logger->log(Log::DEBUG, \"Creating the PointerManager!\");\n            g_pPointerManager = makeUnique<CPointerManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the EventManager!\");\n            g_pEventManager = makeUnique<CEventManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the AsyncResourceGatherer!\");\n            g_pAsyncResourceGatherer = makeUnique<Hyprgraphics::CAsyncResourceGatherer>();\n        } break;\n        case STAGE_BASICINIT: {\n            Log::logger->log(Log::DEBUG, \"Creating the CHyprOpenGLImpl!\");\n            g_pHyprOpenGL = makeUnique<CHyprOpenGLImpl>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the HyprRenderer!\");\n            g_pHyprRenderer = makeUnique<CHyprGLRenderer>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the ProtocolManager!\");\n            g_pProtocolManager = makeUnique<CProtocolManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the SeatManager!\");\n            g_pSeatManager = makeUnique<CSeatManager>();\n\n            // init focus state els\n            Desktop::History::windowTracker();\n            Desktop::History::workspaceTracker();\n\n        } break;\n        case STAGE_LATE: {\n            Log::logger->log(Log::DEBUG, \"Creating CHyprCtl\");\n            g_pHyprCtl = makeUnique<CHyprCtl>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the InputManager!\");\n            g_pInputManager = makeUnique<CInputManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the XWaylandManager!\");\n            g_pXWaylandManager = makeUnique<CHyprXWaylandManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the SessionLockManager!\");\n            g_pSessionLockManager = makeUnique<CSessionLockManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the HyprDebugOverlay!\");\n            g_pDebugOverlay = makeUnique<CHyprDebugOverlay>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the HyprNotificationOverlay!\");\n            g_pHyprNotificationOverlay = makeUnique<CHyprNotificationOverlay>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the PluginSystem!\");\n            g_pPluginSystem = makeUnique<CPluginSystem>();\n            g_pConfigManager->handlePluginLoads();\n\n            Log::logger->log(Log::DEBUG, \"Creating the DecorationPositioner!\");\n            g_pDecorationPositioner = makeUnique<CDecorationPositioner>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the CursorManager!\");\n            g_pCursorManager = makeUnique<CCursorManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the VersionKeeper!\");\n            g_pVersionKeeperMgr = makeUnique<CVersionKeeperManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the DonationNag!\");\n            g_pDonationNagManager = makeUnique<CDonationNagManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the WelcomeManager!\");\n            g_pWelcomeManager = makeUnique<CWelcomeManager>();\n\n            Log::logger->log(Log::DEBUG, \"Creating the ANRManager!\");\n            g_pANRManager = makeUnique<CANRManager>();\n\n            Log::logger->log(Log::DEBUG, \"Starting XWayland\");\n            g_pXWayland = makeUnique<CXWayland>(g_pCompositor->m_wantsXwayland);\n        } break;\n        default: UNREACHABLE();\n    }\n}\n\nvoid CCompositor::createLockFile() {\n    const auto    PATH = m_instancePath + \"/hyprland.lock\";\n\n    std::ofstream ofs(PATH, std::ios::trunc);\n\n    ofs << m_hyprlandPID << \"\\n\" << m_wlDisplaySocket << \"\\n\";\n\n    ofs.close();\n}\n\nvoid CCompositor::removeLockFile() {\n    const auto PATH = m_instancePath + \"/hyprland.lock\";\n\n    if (std::filesystem::exists(PATH))\n        std::filesystem::remove(PATH);\n}\n\nvoid CCompositor::prepareFallbackOutput() {\n    // create a backup monitor\n    SP<Aquamarine::IBackendImplementation> headless;\n    for (auto const& impl : m_aqBackend->getImplementations()) {\n        if (impl->type() == Aquamarine::AQ_BACKEND_HEADLESS) {\n            headless = impl;\n            break;\n        }\n    }\n\n    if (!headless) {\n        Log::logger->log(Log::WARN, \"No headless in prepareFallbackOutput?!\");\n        return;\n    }\n\n    headless->createOutput();\n\n    if (m_monitors.empty())\n        enterUnsafeState();\n}\n\nvoid CCompositor::startCompositor() {\n    signal(SIGPIPE, SIG_IGN);\n\n    if (\n        /* Session-less Hyprland usually means a nest, don't update the env in that case */\n        m_aqBackend->hasSession() &&\n        /* Activation environment management is not disabled */\n        !Env::envEnabled(\"HYPRLAND_NO_SD_VARS\")) {\n        const auto CMD =\n#ifdef USES_SYSTEMD\n            \"systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash \"\n            \"dbus-update-activation-environment 2>/dev/null && \"\n#endif\n            \"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS\";\n        CKeybindManager::spawn(CMD);\n    }\n\n    Log::logger->log(Log::DEBUG, \"Running on WAYLAND_DISPLAY: {}\", m_wlDisplaySocket);\n\n    prepareFallbackOutput();\n\n    g_pHyprRenderer->setCursorFromName(\"left_ptr\");\n\n#ifdef USES_SYSTEMD\n    if (NSystemd::sdBooted() > 0) {\n        // tell systemd that we are ready so it can start other bond, following, related units\n        if (!Env::envEnabled(\"HYPRLAND_NO_SD_NOTIFY\"))\n            NSystemd::sdNotify(0, \"READY=1\");\n    } else\n        Log::logger->log(Log::DEBUG, \"systemd integration is baked in but system itself is not booted à la systemd!\");\n#endif\n\n    createLockFile();\n\n    Event::bus()->m_events.ready.emit();\n\n    if (m_watchdogWriteFd.isValid())\n        write(m_watchdogWriteFd.get(), \"vax\", 3);\n\n    // This blocks until we are done.\n    Log::logger->log(Log::DEBUG, \"Hyprland is ready, running the event loop!\");\n    g_pEventLoopManager->enterLoop();\n}\n\nPHLMONITOR CCompositor::getMonitorFromID(const MONITORID& id) {\n    for (auto const& m : m_monitors) {\n        if (m->m_id == id) {\n            return m;\n        }\n    }\n\n    return nullptr;\n}\n\nPHLMONITOR CCompositor::getMonitorFromName(const std::string& name) {\n    for (auto const& m : m_monitors) {\n        if (m->m_name == name) {\n            return m;\n        }\n    }\n    return nullptr;\n}\n\nPHLMONITOR CCompositor::getMonitorFromDesc(const std::string& desc) {\n    for (auto const& m : m_monitors) {\n        if (m->m_description.starts_with(desc))\n            return m;\n    }\n    return nullptr;\n}\n\nPHLMONITOR CCompositor::getMonitorFromCursor() {\n    return getMonitorFromVector(g_pPointerManager->position());\n}\n\nPHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) {\n    if (m_monitors.empty()) {\n        Log::logger->log(Log::WARN, \"getMonitorFromVector called with empty monitor list\");\n        return nullptr;\n    }\n\n    PHLMONITOR mon;\n    for (auto const& m : m_monitors) {\n        if (CBox{m->m_position, m->m_size}.containsPoint(point)) {\n            mon = m;\n            break;\n        }\n    }\n\n    if (!mon) {\n        float      bestDistance = 0.f;\n        PHLMONITOR pBestMon;\n\n        for (auto const& m : m_monitors) {\n            float dist = vecToRectDistanceSquared(point, m->m_position, m->m_position + m->m_size);\n\n            if (dist < bestDistance || !pBestMon) {\n                bestDistance = dist;\n                pBestMon     = m;\n            }\n        }\n\n        if (!pBestMon) { // ?????\n            Log::logger->log(Log::WARN, \"getMonitorFromVector no close mon???\");\n            return m_monitors.front();\n        }\n\n        return pBestMon;\n    }\n\n    return mon;\n}\n\nvoid CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) {\n    if (!pWindow->m_fadingOut) {\n        Event::bus()->m_events.window.destroy.emit(pWindow);\n\n        std::erase_if(m_windows, [&](SP<Desktop::View::CWindow>& el) { return el == pWindow; });\n        std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; });\n    }\n}\n\nbool CCompositor::monitorExists(PHLMONITOR pMonitor) {\n    return std::ranges::any_of(m_realMonitors, [&](const PHLMONITOR& m) { return m == pMonitor; });\n}\n\nPHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) {\n    const auto PMONITOR = getMonitorFromVector(pos);\n    if (!PMONITOR)\n        return nullptr;\n\n    static auto PRESIZEONBORDER      = CConfigValue<Hyprlang::INT>(\"general:resize_on_border\");\n    static auto PBORDERSIZE          = CConfigValue<Hyprlang::INT>(\"general:border_size\");\n    static auto PBORDERGRABEXTEND    = CConfigValue<Hyprlang::INT>(\"general:extend_border_grab_area\");\n    static auto PSPECIALFALLTHRU     = CConfigValue<Hyprlang::INT>(\"input:special_fallthrough\");\n    static auto PMODALPARENTBLOCKING = CConfigValue<Hyprlang::INT>(\"general:modal_parent_blocking\");\n    const auto  BORDER_GRAB_AREA     = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0;\n    const bool  ONLY_PRIORITY        = properties & Desktop::View::FOCUS_PRIORITY;\n\n    const auto  isShadowedByModal = [](PHLWINDOW w) -> bool {\n        return *PMODALPARENTBLOCKING && w->m_xdgSurface && w->m_xdgSurface->m_toplevel && w->m_xdgSurface->m_toplevel->anyChildModal();\n    };\n\n    // pinned windows on top of floating regardless\n    if (properties & Desktop::View::ALLOW_FLOATING) {\n        for (auto const& w : m_windows | std::views::reverse) {\n            if (ONLY_PRIORITY && !w->priorityFocus())\n                continue;\n\n            if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() &&\n                w != pIgnoreWindow && !isShadowedByModal(w)) {\n                const auto BB  = w->getWindowBoxUnified(properties);\n                CBox       box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0);\n                if (box.containsPoint(g_pPointerManager->position()))\n                    return w;\n\n                if (!w->m_isX11) {\n                    if (w->hasPopupAt(pos))\n                        return w;\n                }\n            }\n        }\n    }\n\n    auto windowForWorkspace = [&](bool special) -> PHLWINDOW {\n        auto floating = [&](bool aboveFullscreen) -> PHLWINDOW {\n            for (auto const& w : m_windows | std::views::reverse) {\n\n                if (special && !w->onSpecialWorkspace()) // because special floating may creep up into regular\n                    continue;\n\n                if (!w->m_workspace)\n                    continue;\n\n                if (ONLY_PRIORITY && !w->priorityFocus())\n                    continue;\n\n                const auto PWINDOWMONITOR = w->m_monitor.lock();\n\n                // to avoid focusing windows behind special workspaces from other monitors\n                if (!*PSPECIALFALLTHRU && PWINDOWMONITOR && PWINDOWMONITOR->m_activeSpecialWorkspace && w->m_workspace != PWINDOWMONITOR->m_activeSpecialWorkspace) {\n                    const auto BB = w->getWindowBoxUnified(properties);\n                    if (BB.x >= PWINDOWMONITOR->m_position.x && BB.y >= PWINDOWMONITOR->m_position.y &&\n                        BB.x + BB.width <= PWINDOWMONITOR->m_position.x + PWINDOWMONITOR->m_size.x && BB.y + BB.height <= PWINDOWMONITOR->m_position.y + PWINDOWMONITOR->m_size.y)\n                        continue;\n                }\n\n                if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() &&\n                    w != pIgnoreWindow && (!aboveFullscreen || w->m_createdOverFullscreen) && !isShadowedByModal(w)) {\n                    // OR windows should add focus to parent\n                    if (w->m_X11ShouldntFocus && !w->isX11OverrideRedirect())\n                        continue;\n\n                    const auto BB  = w->getWindowBoxUnified(properties);\n                    CBox       box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0);\n                    if (box.containsPoint(g_pPointerManager->position())) {\n\n                        if (w->m_isX11 && w->isX11OverrideRedirect() && !w->m_xwaylandSurface->wantsFocus()) {\n                            // Override Redirect\n                            return Desktop::focusState()->window(); // we kinda trick everything here.\n                            // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases.\n                        }\n\n                        return w;\n                    }\n\n                    if (!w->m_isX11) {\n                        if (w->hasPopupAt(pos))\n                            return w;\n                    }\n                }\n            }\n\n            return nullptr;\n        };\n\n        if (properties & Desktop::View::ALLOW_FLOATING) {\n            // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter.\n            auto found = floating(true);\n            if (found)\n                return found;\n        }\n\n        if (properties & Desktop::View::FLOATING_ONLY)\n            return floating(false);\n\n        const WORKSPACEID WSPID      = special ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID();\n        const auto        PWORKSPACE = getWorkspaceByID(WSPID);\n\n        if (PWORKSPACE->m_hasFullscreenWindow && !(properties & Desktop::View::SKIP_FULLSCREEN_PRIORITY) && !ONLY_PRIORITY) {\n            const auto FS_WINDOW = PWORKSPACE->getFullscreenWindow();\n\n            if (!FS_WINDOW)\n                return nullptr;\n\n            // for maximized windows, don't return a window if we are not directly on it.\n            if (FS_WINDOW->m_fullscreenState.internal != FSMODE_MAXIMIZED || FS_WINDOW->getWindowBoxUnified(properties).containsPoint(pos))\n                return PWORKSPACE->getFullscreenWindow();\n            else\n                return nullptr;\n        }\n\n        auto found = floating(false);\n        if (found)\n            return found;\n\n        // for windows, we need to check their extensions too, first.\n        for (auto const& w : m_windows) {\n            if (ONLY_PRIORITY && !w->priorityFocus())\n                continue;\n\n            if (special != w->onSpecialWorkspace())\n                continue;\n\n            if (!w->m_workspace)\n                continue;\n\n            if (!w->m_isX11 && !w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus &&\n                !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) {\n                if (w->hasPopupAt(pos))\n                    return w;\n            }\n        }\n\n        for (auto const& w : m_windows) {\n            if (ONLY_PRIORITY && !w->priorityFocus())\n                continue;\n\n            if (special != w->onSpecialWorkspace())\n                continue;\n\n            if (!w->m_workspace)\n                continue;\n\n            if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() &&\n                w != pIgnoreWindow && !isShadowedByModal(w)) {\n                CBox box = (properties & Desktop::View::USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size};\n                if ((properties & Desktop::View::INPUT_EXTENTS) && BORDER_GRAB_AREA > 0 && !w->isX11OverrideRedirect())\n                    box.expand(BORDER_GRAB_AREA);\n                if (box.containsPoint(pos))\n                    return w;\n            }\n        }\n\n        return nullptr;\n    };\n\n    // special workspace\n    if (PMONITOR->m_activeSpecialWorkspace && !*PSPECIALFALLTHRU)\n        return windowForWorkspace(true);\n\n    if (PMONITOR->m_activeSpecialWorkspace) {\n        const auto PWINDOW = windowForWorkspace(true);\n\n        if (PWINDOW)\n            return PWINDOW;\n    }\n\n    return windowForWorkspace(false);\n}\n\nSP<CWLSurfaceResource> CCompositor::vectorWindowToSurface(const Vector2D& pos, PHLWINDOW pWindow, Vector2D& sl) {\n\n    if (!validMapped(pWindow))\n        return nullptr;\n\n    RASSERT(!pWindow->m_isX11, \"Cannot call vectorWindowToSurface on an X11 window!\");\n\n    // try popups first\n    const auto PPOPUP = pWindow->m_popupHead->at(pos);\n\n    if (PPOPUP) {\n        const auto OFF = PPOPUP->coordsRelativeToParent();\n        sl             = pos - pWindow->m_realPosition->goal() - OFF;\n        return PPOPUP->wlSurface()->resource();\n    }\n\n    auto [surf, local] = pWindow->wlSurface()->resource()->at(pos - pWindow->m_realPosition->goal(), true);\n    if (surf) {\n        sl = local;\n        return surf;\n    }\n\n    return nullptr;\n}\n\nVector2D CCompositor::vectorToSurfaceLocal(const Vector2D& vec, PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface) {\n    if (!validMapped(pWindow))\n        return {};\n\n    if (pWindow->m_isX11)\n        return vec - pWindow->m_realPosition->goal();\n\n    const auto PPOPUP = pWindow->m_popupHead->at(vec);\n    if (PPOPUP)\n        return vec - PPOPUP->coordsGlobal();\n\n    std::tuple<SP<CWLSurfaceResource>, Vector2D> iterData = {pSurface, {-1337, -1337}};\n\n    pWindow->wlSurface()->resource()->breadthfirst(\n        [](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) {\n            const auto PDATA = sc<std::tuple<SP<CWLSurfaceResource>, Vector2D>*>(data);\n            if (surf == std::get<0>(*PDATA))\n                std::get<1>(*PDATA) = offset;\n        },\n        &iterData);\n\n    CBox geom = pWindow->m_xdgSurface->m_current.geometry;\n\n    if (std::get<1>(iterData) == Vector2D{-1337, -1337})\n        return vec - pWindow->m_realPosition->goal();\n\n    return vec - pWindow->m_realPosition->goal() - std::get<1>(iterData) + Vector2D{geom.x, geom.y};\n}\n\nPHLMONITOR CCompositor::getMonitorFromOutput(SP<Aquamarine::IOutput> out) {\n    for (auto const& m : m_monitors) {\n        if (m->m_output == out) {\n            return m;\n        }\n    }\n\n    return nullptr;\n}\n\nPHLMONITOR CCompositor::getRealMonitorFromOutput(SP<Aquamarine::IOutput> out) {\n    for (auto const& m : m_realMonitors) {\n        if (m->m_output == out) {\n            return m;\n        }\n    }\n\n    return nullptr;\n}\n\nSP<CWLSurfaceResource> CCompositor::vectorToLayerPopupSurface(const Vector2D& pos, PHLMONITOR monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) {\n    for (auto const& lsl : monitor->m_layerSurfaceLayers | std::views::reverse) {\n        for (auto const& ls : lsl | std::views::reverse) {\n            if (!ls->aliveAndVisible())\n                continue;\n\n            auto SURFACEAT = ls->m_popupHead->at(pos, true);\n\n            if (SURFACEAT) {\n                *ppLayerSurfaceFound = ls.lock();\n                *sCoords             = pos - SURFACEAT->coordsGlobal();\n                return SURFACEAT->wlSurface()->resource();\n            }\n        }\n    }\n\n    return nullptr;\n}\n\nSP<CWLSurfaceResource> CCompositor::vectorToLayerSurface(const Vector2D& pos, std::vector<PHLLSREF>* layerSurfaces, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound,\n                                                         bool aboveLockscreen) {\n\n    for (auto const& ls : *layerSurfaces | std::views::reverse) {\n        if (!ls->aliveAndVisible() || (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2))\n            continue;\n\n        auto [surf, local] = ls->m_layerSurface->m_surface->at(pos - ls->m_geometry.pos(), true);\n\n        if (surf) {\n            if (surf->m_current.input.empty())\n                continue;\n\n            *ppLayerSurfaceFound = ls.lock();\n\n            *sCoords = local;\n\n            return surf;\n        }\n    }\n\n    return nullptr;\n}\n\nPHLWINDOW CCompositor::getWindowFromSurface(SP<CWLSurfaceResource> pSurface) {\n    if (!pSurface || !pSurface->m_hlSurface)\n        return nullptr;\n\n    const auto VIEW = pSurface->m_hlSurface->view();\n\n    if (!VIEW || VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW)\n        return nullptr;\n\n    return dynamicPointerCast<Desktop::View::CWindow>(VIEW);\n}\n\nPHLWINDOW CCompositor::getWindowFromHandle(uint32_t handle) {\n    for (auto const& w : m_windows) {\n        if (sc<uint32_t>(rc<uint64_t>(w.get()) & 0xFFFFFFFF) == handle) {\n            return w;\n        }\n    }\n\n    return nullptr;\n}\n\nPHLWORKSPACE CCompositor::getWorkspaceByID(const WORKSPACEID& id) {\n    for (auto const& w : getWorkspaces()) {\n        if (w->m_id == id && !w->inert())\n            return w.lock();\n    }\n\n    return nullptr;\n}\n\nPHLWINDOW CCompositor::getUrgentWindow() {\n    for (auto const& w : m_windows) {\n        if (w->m_isMapped && w->m_isUrgent)\n            return w;\n    }\n\n    return nullptr;\n}\n\nbool CCompositor::isWindowActive(PHLWINDOW pWindow) {\n    if (!Desktop::focusState()->window() && !Desktop::focusState()->surface())\n        return false;\n\n    if (!pWindow->m_isMapped)\n        return false;\n\n    const auto PSURFACE = pWindow->wlSurface()->resource();\n\n    return PSURFACE == Desktop::focusState()->surface() || pWindow == Desktop::focusState()->window();\n}\n\nvoid CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) {\n    if (!validMapped(pWindow))\n        return;\n\n    if (top)\n        pWindow->m_createdOverFullscreen = true;\n\n    if (pWindow == (top ? m_windows.back() : m_windows.front()))\n        return;\n\n    auto moveToZ = [&](PHLWINDOW pw, bool top) -> void {\n        if (top) {\n            for (auto it = m_windows.begin(); it != m_windows.end(); ++it) {\n                if (*it == pw) {\n                    std::rotate(it, it + 1, m_windows.end());\n                    break;\n                }\n            }\n        } else {\n            for (auto it = m_windows.rbegin(); it != m_windows.rend(); ++it) {\n                if (*it == pw) {\n                    std::rotate(it, it + 1, m_windows.rend());\n                    break;\n                }\n            }\n        }\n\n        if (pw->m_isMapped)\n            g_pHyprRenderer->damageMonitor(pw->m_monitor.lock());\n    };\n\n    if (!pWindow->m_isX11)\n        moveToZ(pWindow, top);\n    else {\n        // move X11 window stack\n\n        std::vector<PHLWINDOW> toMove;\n\n        auto                   x11Stack = [&](PHLWINDOW pw, bool top, auto&& x11Stack) -> void {\n            if (top)\n                toMove.emplace_back(pw);\n            else\n                toMove.insert(toMove.begin(), pw);\n\n            for (auto const& w : m_windows) {\n                if (w->m_isMapped && !w->isHidden() && w->m_isX11 && w->x11TransientFor() == pw && w != pw && std::ranges::find(toMove, w) == toMove.end()) {\n                    x11Stack(w, top, x11Stack);\n                }\n            }\n        };\n\n        x11Stack(pWindow, top, x11Stack);\n        for (const auto& it : toMove) {\n            moveToZ(it, top);\n        }\n    }\n}\n\nvoid CCompositor::cleanupFadingOut(const MONITORID& monid) {\n    for (auto const& ww : m_windowsFadingOut) {\n\n        auto w = ww.lock();\n\n        if (w->monitorID() != monid && w->m_monitor)\n            continue;\n\n        if (!w->m_fadingOut || w->m_alpha->value() == 0.f) {\n\n            w->m_fadingOut = false;\n\n            if (!w->m_readyToDelete)\n                continue;\n\n            removeWindowFromVectorSafe(w);\n\n            w.reset();\n\n            Log::logger->log(Log::DEBUG, \"Cleanup: destroyed a window\");\n            return;\n        }\n    }\n\n    bool layersDirty = false;\n\n    for (auto const& lsr : m_surfacesFadingOut) {\n\n        auto ls = lsr.lock();\n\n        if (!ls) {\n            layersDirty = true;\n            continue;\n        }\n\n        if (ls->monitorID() != monid && ls->m_monitor)\n            continue;\n\n        // mark blur for recalc\n        if (ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) {\n            auto mon = getMonitorFromID(monid);\n            if (mon)\n                mon->m_blurFBDirty = true;\n        }\n\n        if (ls->m_fadingOut && ls->m_readyToDelete && ls->isFadedOut()) {\n            for (auto const& m : m_monitors) {\n                for (auto& lsl : m->m_layerSurfaceLayers) {\n                    if (!lsl.empty() && std::ranges::find_if(lsl, [&](auto& other) { return other == ls; }) != lsl.end()) {\n                        std::erase_if(lsl, [&](auto& other) { return other == ls || !other; });\n                    }\n                }\n            }\n\n            std::erase_if(m_surfacesFadingOut, [ls](const auto& el) { return el.lock() == ls; });\n            std::erase_if(m_layers, [ls](const auto& el) { return el == ls; });\n\n            ls.reset();\n\n            Log::logger->log(Log::DEBUG, \"Cleanup: destroyed a layersurface\");\n\n            return;\n        }\n    }\n\n    if (layersDirty)\n        std::erase_if(m_surfacesFadingOut, [](const auto& el) { return el.expired(); });\n}\n\nvoid CCompositor::addToFadingOutSafe(PHLLS pLS) {\n    const auto FOUND = std::ranges::find_if(m_surfacesFadingOut, [&](auto& other) { return other.lock() == pLS; });\n\n    if (FOUND != m_surfacesFadingOut.end())\n        return; // if it's already added, don't add it.\n\n    m_surfacesFadingOut.emplace_back(pLS);\n}\n\nvoid CCompositor::removeFromFadingOutSafe(PHLLS ls) {\n    std::erase(m_surfacesFadingOut, ls);\n}\n\nvoid CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) {\n    const auto FOUND = std::ranges::find_if(m_windowsFadingOut, [&](PHLWINDOWREF& other) { return other.lock() == pWindow; });\n\n    if (FOUND != m_windowsFadingOut.end())\n        return; // if it's already added, don't add it.\n\n    m_windowsFadingOut.emplace_back(pWindow);\n}\n\nPHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) {\n    if (dir == Math::DIRECTION_DEFAULT)\n        return nullptr;\n\n    const auto PMONITOR = pWindow->m_monitor.lock();\n\n    if (!PMONITOR)\n        return nullptr; // ??\n\n    const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{PMONITOR->m_position, PMONITOR->m_size} : pWindow->getWindowIdealBoundingBoxIgnoreReserved();\n    const auto PWORKSPACE    = pWindow->m_workspace;\n\n    if (!PWORKSPACE)\n        return nullptr; // ??\n\n    return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating);\n}\n\nPHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {\n    if (dir == Math::DIRECTION_DEFAULT)\n        return nullptr;\n\n    // 0 -> history, 1 -> shared length\n    static auto PMETHOD          = CConfigValue<Hyprlang::INT>(\"binds:focus_preferred_method\");\n    static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>(\"binds:window_direction_monitor_fallback\");\n\n    const auto  POSA  = box.pos();\n    const auto  SIZEA = box.size();\n\n    auto        leaderValue  = -1;\n    PHLWINDOW   leaderWindow = nullptr;\n\n    if (!useVectorAngles) {\n        // helper to check if two rectangles are adjacent along an axis, considering slight overlaps.\n        // returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension.\n        static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool {\n            constexpr double STICK_THRESHOLD   = 2.0;\n            constexpr double MAX_OVERLAP_RATIO = 0.5;\n\n            const double     aEdge = aMin;\n            const double     bEdge = bMax;\n            const double     delta = aEdge - bEdge;\n\n            // old STICKS check for 2px\n            if (std::abs(delta) < STICK_THRESHOLD)\n                return true;\n\n            if (delta >= 0)\n                return false;\n\n            const double overlap = -delta;\n            const double sizeA   = aMax - aMin;\n            const double sizeB   = bMax - bMin;\n\n            // reject if one rectangle fully contains the other\n            if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax))\n                return false;\n\n            // accept if overlap is at most 50% of the smaller dimension\n            return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO;\n        };\n\n        for (auto const& w : m_windows) {\n            if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())\n                continue;\n\n            if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace)\n                continue;\n\n            if (pWorkspace->m_hasFullscreenWindow && !w->isFullscreen() && !w->m_createdOverFullscreen)\n                continue;\n\n            if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor)\n                continue;\n\n            const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved();\n\n            const auto POSB  = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y);\n            const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height);\n\n            double     intersectLength = -1;\n\n            switch (dir) {\n                case Math::DIRECTION_LEFT:\n                    if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x))\n                        intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));\n                    break;\n                case Math::DIRECTION_RIGHT:\n                    if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x))\n                        intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));\n                    break;\n                case Math::DIRECTION_UP:\n                    if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y))\n                        intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));\n                    break;\n                case Math::DIRECTION_DOWN:\n                    if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y))\n                        intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));\n                    break;\n                default: break;\n            }\n\n            if (*PMETHOD == 0 /* history */) {\n                if (intersectLength > 0) {\n\n                    // get idx\n                    int         windowIDX = -1;\n                    const auto& HISTORY   = Desktop::History::windowTracker()->fullHistory();\n                    for (int64_t i = HISTORY.size() - 1; i >= 0; --i) {\n                        if (HISTORY[i] == w) {\n                            windowIDX = i;\n                            break;\n                        }\n                    }\n\n                    if (windowIDX > leaderValue) {\n                        leaderValue  = windowIDX;\n                        leaderWindow = w;\n                    }\n                }\n            } else /* length */ {\n                if (intersectLength > leaderValue) {\n                    leaderValue  = intersectLength;\n                    leaderWindow = w;\n                }\n            }\n        }\n    } else {\n        static const std::unordered_map<Math::eDirection, Vector2D> VECTORS = {\n            {Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}};\n\n        //\n        auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double {\n            double dot = (a.x * b.x) + (a.y * b.y);\n            double ang = std::acos(dot / (a.size() * b.size()));\n            return ang;\n        };\n\n        float           bestAngleAbs = 2.0 * M_PI;\n        constexpr float THRESHOLD    = 0.3 * M_PI;\n\n        for (auto const& w : m_windows) {\n            if (w == ignoreWindow || !w->m_isMapped || !w->m_workspace || w->isHidden() || (!w->isFullscreen() && !w->m_isFloating) || !w->m_workspace->isVisible())\n                continue;\n\n            if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace)\n                continue;\n\n            if (pWorkspace->m_hasFullscreenWindow && !w->isFullscreen() && !w->m_createdOverFullscreen)\n                continue;\n\n            if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor)\n                continue;\n\n            const auto DIST  = w->middle().distance(box.middle());\n            const auto ANGLE = vectorAngles(Vector2D{w->middle() - box.middle()}, VECTORS.at(dir));\n\n            if (ANGLE > M_PI_2)\n                continue; // if the angle is over 90 degrees, ignore. Wrong direction entirely.\n\n            if ((bestAngleAbs < THRESHOLD && DIST < leaderValue && ANGLE < THRESHOLD) || (ANGLE < bestAngleAbs && bestAngleAbs > THRESHOLD) || leaderValue == -1) {\n                leaderValue  = DIST;\n                bestAngleAbs = ANGLE;\n                leaderWindow = w;\n            }\n        }\n\n        if (!leaderWindow && pWorkspace->m_hasFullscreenWindow)\n            leaderWindow = pWorkspace->getFullscreenWindow();\n    }\n\n    if (leaderValue != -1)\n        return leaderWindow;\n\n    return nullptr;\n}\n\ntemplate <typename WINDOWPTR>\nstatic bool isWorkspaceMatches(WINDOWPTR pWindow, const WINDOWPTR w, bool anyWorkspace) {\n    return anyWorkspace ? w->m_workspace && w->m_workspace->isVisible() : w->m_workspace == pWindow->m_workspace;\n}\n\ntemplate <typename WINDOWPTR>\nstatic bool isFloatingMatches(WINDOWPTR w, std::optional<bool> floating) {\n    return !floating.has_value() || w->m_isFloating == floating.value();\n}\n\ntemplate <typename WINDOWPTR>\nstatic bool isWindowAvailableForCycle(WINDOWPTR pWindow, WINDOWPTR w, bool focusableOnly, std::optional<bool> floating, bool anyWorkspace = false) {\n    return isFloatingMatches(w, floating) &&\n        (w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_ruleApplicator->noFocus().valueOrDefault()));\n}\n\ntemplate <typename Iterator>\nstatic PHLWINDOW getWindowPred(Iterator cur, Iterator end, Iterator begin, const std::function<bool(const PHLWINDOW&)> PRED) {\n    const auto IN_ONE_SIDE = std::find_if(cur, end, PRED);\n    if (IN_ONE_SIDE != end)\n        return *IN_ONE_SIDE;\n    const auto IN_OTHER_SIDE = std::find_if(begin, cur, PRED);\n    return *IN_OTHER_SIDE;\n}\n\ntemplate <typename Iterator>\nstatic PHLWINDOW getWeakWindowPred(Iterator cur, Iterator end, Iterator begin, const std::function<bool(const PHLWINDOWREF&)> PRED) {\n    const auto IN_ONE_SIDE = std::find_if(cur, end, PRED);\n    if (IN_ONE_SIDE != end)\n        return IN_ONE_SIDE->lock();\n    const auto IN_OTHER_SIDE = std::find_if(begin, cur, PRED);\n    return IN_OTHER_SIDE->lock();\n}\n\nPHLWINDOW CCompositor::getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly, std::optional<bool> floating, bool visible, bool next) {\n    const auto FINDER = [&](const PHLWINDOWREF& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); };\n    // also m_vWindowFocusHistory has reverse order, so when it is next - we need to reverse again\n    const auto& HISTORY = Desktop::History::windowTracker()->fullHistory();\n    return next ? getWeakWindowPred(std::ranges::find(HISTORY, cur), HISTORY.end(), HISTORY.begin(), FINDER) :\n                  getWeakWindowPred(std::ranges::find(HISTORY | std::views::reverse, cur), HISTORY.rend(), HISTORY.rbegin(), FINDER);\n}\n\nPHLWINDOW CCompositor::getWindowCycle(PHLWINDOW cur, bool focusableOnly, std::optional<bool> floating, bool visible, bool prev) {\n    const auto FINDER = [&](const PHLWINDOW& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); };\n    return prev ? getWindowPred(std::ranges::find(m_windows | std::views::reverse, cur), m_windows.rend(), m_windows.rbegin(), FINDER) :\n                  getWindowPred(std::ranges::find(m_windows, cur), m_windows.end(), m_windows.begin(), FINDER);\n}\n\nWORKSPACEID CCompositor::getNextAvailableNamedWorkspace() {\n    WORKSPACEID lowest = -1337 + 1;\n    for (auto const& w : getWorkspaces()) {\n        if (w->m_id < -1 && w->m_id < lowest)\n            lowest = w->m_id;\n    }\n\n    // Give priority to persistent workspaces to avoid any conflicts between them.\n    for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) {\n        if (!rule.isPersistent)\n            continue;\n        if (rule.workspaceId < -1 && rule.workspaceId < lowest)\n            lowest = rule.workspaceId;\n    }\n\n    return lowest - 1;\n}\n\nPHLWORKSPACE CCompositor::getWorkspaceByName(const std::string& name) {\n    for (auto const& w : getWorkspaces()) {\n        if (w->m_name == name && !w->inert())\n            return w.lock();\n    }\n\n    return nullptr;\n}\n\nPHLWORKSPACE CCompositor::getWorkspaceByString(const std::string& str) {\n    if (str.starts_with(\"name:\")) {\n        return getWorkspaceByName(str.substr(str.find_first_of(':') + 1));\n    }\n\n    try {\n        return getWorkspaceByID(getWorkspaceIDNameFromString(str).id);\n    } catch (std::exception& e) { Log::logger->log(Log::ERR, \"Error in getWorkspaceByString, invalid id\"); }\n\n    return nullptr;\n}\n\nbool CCompositor::isPointOnAnyMonitor(const Vector2D& point) {\n    return std::ranges::any_of(\n        m_monitors, [&](const PHLMONITOR& m) { return VECINRECT(point, m->m_position.x, m->m_position.y, m->m_size.x + m->m_position.x, m->m_size.y + m->m_position.y); });\n}\n\nbool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR pMonitor) {\n    const auto PMONITOR = pMonitor ? pMonitor : getMonitorFromVector(point);\n\n    auto       box = PMONITOR->logicalBox();\n    if (VECNOTINRECT(point, box.x - 1, box.y - 1, box.x + box.w + 1, box.y + box.h + 1))\n        return false;\n\n    PMONITOR->m_reservedArea.applyip(box);\n\n    return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h);\n}\n\nstd::optional<CBox> CCompositor::calculateX11WorkArea() {\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n    // We more than likely won't be able to calculate one\n    // and even if we could this is minor\n    if (m_monitors.size() > 1 || m_monitors.empty())\n        return std::nullopt;\n\n    const auto M = m_monitors.front();\n\n    // we ignore monitor->m_position on purpose\n    CBox box = M->logicalBoxMinusReserved().translate(-M->m_position);\n    if ((*PXWLFORCESCALEZERO))\n        box.scale(M->m_scale);\n\n    return box.translate(M->m_xwaylandPosition);\n}\n\nPHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) {\n    return getMonitorInDirection(Desktop::focusState()->monitor(), dir);\n}\n\nPHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) {\n    if (!pSourceMonitor)\n        return nullptr;\n\n    const auto POSA  = pSourceMonitor->m_position;\n    const auto SIZEA = pSourceMonitor->m_size;\n\n    auto       longestIntersect        = -1;\n    PHLMONITOR longestIntersectMonitor = nullptr;\n\n    for (auto const& m : m_monitors) {\n        if (m == pSourceMonitor)\n            continue;\n\n        const auto POSB  = m->m_position;\n        const auto SIZEB = m->m_size;\n        switch (dir) {\n            case Math::DIRECTION_LEFT:\n                if (STICKS(POSA.x, POSB.x + SIZEB.x)) {\n                    const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));\n                    if (INTERSECTLEN > longestIntersect) {\n                        longestIntersect        = INTERSECTLEN;\n                        longestIntersectMonitor = m;\n                    }\n                }\n                break;\n            case Math::DIRECTION_RIGHT:\n                if (STICKS(POSA.x + SIZEA.x, POSB.x)) {\n                    const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));\n                    if (INTERSECTLEN > longestIntersect) {\n                        longestIntersect        = INTERSECTLEN;\n                        longestIntersectMonitor = m;\n                    }\n                }\n                break;\n            case Math::DIRECTION_UP:\n                if (STICKS(POSA.y, POSB.y + SIZEB.y)) {\n                    const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));\n                    if (INTERSECTLEN > longestIntersect) {\n                        longestIntersect        = INTERSECTLEN;\n                        longestIntersectMonitor = m;\n                    }\n                }\n                break;\n            case Math::DIRECTION_DOWN:\n                if (STICKS(POSA.y + SIZEA.y, POSB.y)) {\n                    const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));\n                    if (INTERSECTLEN > longestIntersect) {\n                        longestIntersect        = INTERSECTLEN;\n                        longestIntersectMonitor = m;\n                    }\n                }\n                break;\n            default: break;\n        }\n    }\n\n    if (longestIntersect != -1)\n        return longestIntersectMonitor;\n\n    return nullptr;\n}\n\nvoid CCompositor::updateAllWindowsAnimatedDecorationValues() {\n    for (auto const& w : m_windows) {\n        if (!w->m_isMapped)\n            continue;\n\n        w->updateDecorationValues();\n    }\n}\n\nMONITORID CCompositor::getNextAvailableMonitorID(std::string const& name) {\n    // reuse ID if it's already in the map, and the monitor with that ID is not being used by another monitor\n    if (m_monitorIDMap.contains(name) && !std::ranges::any_of(m_realMonitors, [&](auto m) { return m->m_id == m_monitorIDMap[name]; }))\n        return m_monitorIDMap[name];\n\n    // otherwise, find minimum available ID that is not in the map\n    std::unordered_set<MONITORID> usedIDs;\n    for (auto const& monitor : m_realMonitors) {\n        usedIDs.insert(monitor->m_id);\n    }\n\n    MONITORID nextID = 0;\n    while (usedIDs.contains(nextID)) {\n        nextID++;\n    }\n    m_monitorIDMap[name] = nextID;\n    return nextID;\n}\n\nvoid CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitorB) {\n    const auto PWORKSPACEA = pMonitorA->m_activeWorkspace;\n    const auto PWORKSPACEB = pMonitorB->m_activeWorkspace;\n\n    PWORKSPACEA->m_monitor = pMonitorB;\n    PWORKSPACEA->m_events.monitorChanged.emit();\n\n    for (auto const& w : m_windows) {\n        if (w->m_workspace == PWORKSPACEA) {\n            if (w->m_pinned) {\n                w->m_workspace = PWORKSPACEB;\n                continue;\n            }\n\n            w->m_monitor = pMonitorB;\n\n            // additionally, move floating and fs windows manually\n            if (w->m_isFloating)\n                w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position));\n\n            if (w->isFullscreen()) {\n                *w->m_realPosition = pMonitorB->m_position;\n                *w->m_realSize     = pMonitorB->m_size;\n            }\n\n            w->updateToplevel();\n        }\n    }\n\n    PWORKSPACEB->m_monitor = pMonitorA;\n    PWORKSPACEB->m_events.monitorChanged.emit();\n\n    for (auto const& w : m_windows) {\n        if (w->m_workspace == PWORKSPACEB) {\n            if (w->m_pinned) {\n                w->m_workspace = PWORKSPACEA;\n                continue;\n            }\n\n            w->m_monitor = pMonitorA;\n\n            // additionally, move floating and fs windows manually\n            if (w->m_isFloating)\n                w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position));\n\n            if (w->isFullscreen()) {\n                *w->m_realPosition = pMonitorA->m_position;\n                *w->m_realSize     = pMonitorA->m_size;\n            }\n\n            w->updateToplevel();\n        }\n    }\n\n    pMonitorA->m_activeWorkspace = PWORKSPACEB;\n    pMonitorB->m_activeWorkspace = PWORKSPACEA;\n\n    g_layoutManager->recalculateMonitor(pMonitorA);\n    g_layoutManager->recalculateMonitor(pMonitorB);\n\n    g_pHyprRenderer->damageMonitor(pMonitorB);\n    g_pHyprRenderer->damageMonitor(pMonitorA);\n\n    g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n        PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n    g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n        PWORKSPACEA, PWORKSPACEA->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n    if (pMonitorA->m_id == Desktop::focusState()->monitor()->m_id || pMonitorB->m_id == Desktop::focusState()->monitor()->m_id) {\n        const auto LASTWIN = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB->getLastFocusedWindow() : PWORKSPACEA->getLastFocusedWindow();\n        Desktop::focusState()->fullWindowFocus(\n            LASTWIN ? LASTWIN :\n                      (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),\n                                                            Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)),\n            Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);\n\n        const auto PNEWWORKSPACE = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB : PWORKSPACEA;\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"workspace\", .data = PNEWWORKSPACE->m_name});\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"workspacev2\", .data = std::format(\"{},{}\", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)});\n        Event::bus()->m_events.workspace.active.emit(PNEWWORKSPACE);\n    }\n\n    // events\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"moveworkspace\", .data = PWORKSPACEA->m_name + \",\" + pMonitorB->m_name});\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"moveworkspacev2\", .data = std::format(\"{},{},{}\", PWORKSPACEA->m_id, PWORKSPACEA->m_name, pMonitorB->m_name)});\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"moveworkspace\", .data = PWORKSPACEB->m_name + \",\" + pMonitorA->m_name});\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"moveworkspacev2\", .data = std::format(\"{},{},{}\", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)});\n\n    Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB);\n    Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA);\n}\n\nPHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {\n    if (name == \"current\")\n        return Desktop::focusState()->monitor();\n    else if (isDirection(name))\n        return getMonitorInDirection(Math::fromChar(name[0]));\n    else if (name[0] == '+' || name[0] == '-') {\n        // relative\n\n        if (m_monitors.size() == 1)\n            return *m_monitors.begin();\n\n        const auto OFFSET = name[0] == '-' ? name : name.substr(1);\n\n        if (!isNumber(OFFSET)) {\n            Log::logger->log(Log::ERR, \"Error in getMonitorFromString: Not a number in relative.\");\n            return nullptr;\n        }\n\n        int offsetLeft = std::stoi(OFFSET);\n        offsetLeft     = offsetLeft < 0 ? -((-offsetLeft) % m_monitors.size()) : offsetLeft % m_monitors.size();\n\n        int currentPlace = 0;\n        for (int i = 0; i < sc<int>(m_monitors.size()); i++) {\n            if (m_monitors[i] == Desktop::focusState()->monitor()) {\n                currentPlace = i;\n                break;\n            }\n        }\n\n        currentPlace += offsetLeft;\n\n        if (currentPlace < 0) {\n            currentPlace = m_monitors.size() + currentPlace;\n        } else {\n            currentPlace = currentPlace % m_monitors.size();\n        }\n\n        if (currentPlace != std::clamp(currentPlace, 0, sc<int>(m_monitors.size()) - 1)) {\n            Log::logger->log(Log::WARN, \"Error in getMonitorFromString: Vaxry's code sucks.\");\n            currentPlace = std::clamp(currentPlace, 0, sc<int>(m_monitors.size()) - 1);\n        }\n\n        return m_monitors[currentPlace];\n    } else if (isNumber(name)) {\n        // change by ID\n        MONITORID monID = MONITOR_INVALID;\n        try {\n            monID = std::stoi(name);\n        } catch (std::exception& e) {\n            // shouldn't happen but jic\n            Log::logger->log(Log::ERR, \"Error in getMonitorFromString: invalid num\");\n            return nullptr;\n        }\n\n        if (monID > -1 && monID < sc<MONITORID>(m_monitors.size())) {\n            return getMonitorFromID(monID);\n        } else {\n            Log::logger->log(Log::ERR, \"Error in getMonitorFromString: invalid arg 1\");\n            return nullptr;\n        }\n    } else {\n        for (auto const& m : m_monitors) {\n            if (!m->m_output)\n                continue;\n\n            if (m->matchesStaticSelector(name)) {\n                return m;\n            }\n        }\n    }\n\n    return nullptr;\n}\n\nvoid CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor, bool noWarpCursor) {\n    static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue<Hyprlang::INT>(\"binds:hide_special_on_workspace_change\");\n\n    if (!pWorkspace || !pMonitor)\n        return;\n\n    if (pWorkspace->m_monitor == pMonitor)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"moveWorkspaceToMonitor: Moving {} to monitor {}\", pWorkspace->m_id, pMonitor->m_id);\n\n    const auto POLDMON = pWorkspace->m_monitor.lock();\n\n    const bool SWITCHINGISACTIVE = POLDMON ? POLDMON->m_activeWorkspace == pWorkspace : false;\n\n    // fix old mon\n    WORKSPACEID nextWorkspaceOnMonitorID = WORKSPACE_INVALID;\n    if (!SWITCHINGISACTIVE)\n        nextWorkspaceOnMonitorID = pWorkspace->m_id;\n    else {\n        PHLWORKSPACE newWorkspace; // for holding a ref to the new workspace that might be created\n\n        for (auto const& w : getWorkspaces()) {\n            if (w->m_monitor == POLDMON && w->m_id != pWorkspace->m_id && !w->m_isSpecialWorkspace) {\n                nextWorkspaceOnMonitorID = w->m_id;\n                break;\n            }\n        }\n\n        if (nextWorkspaceOnMonitorID == WORKSPACE_INVALID) {\n            nextWorkspaceOnMonitorID = 1;\n\n            while (getWorkspaceByID(nextWorkspaceOnMonitorID) || [&]() -> bool {\n                const auto B = g_pConfigManager->getBoundMonitorForWS(std::to_string(nextWorkspaceOnMonitorID));\n                return B && B != POLDMON;\n            }())\n                nextWorkspaceOnMonitorID++;\n\n            Log::logger->log(Log::DEBUG, \"moveWorkspaceToMonitor: Plugging gap with new {}\", nextWorkspaceOnMonitorID);\n\n            if (POLDMON)\n                newWorkspace = g_pCompositor->createNewWorkspace(nextWorkspaceOnMonitorID, POLDMON->m_id);\n        }\n\n        Log::logger->log(Log::DEBUG, \"moveWorkspaceToMonitor: Plugging gap with existing {}\", nextWorkspaceOnMonitorID);\n        if (POLDMON)\n            POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, true);\n    }\n\n    // move the workspace\n    pWorkspace->m_monitor = pMonitor;\n    pWorkspace->m_space->recheckWorkArea();\n    pWorkspace->m_events.monitorChanged.emit();\n\n    for (auto const& w : m_windows) {\n        if (w->m_workspace == pWorkspace) {\n            if (w->m_pinned) {\n                w->m_workspace = g_pCompositor->getWorkspaceByID(nextWorkspaceOnMonitorID);\n                continue;\n            }\n\n            w->m_monitor = pMonitor;\n\n            // additionally, move floating and fs windows manually\n            if (w->m_isMapped && !w->isHidden()) {\n                if (POLDMON) {\n                    if (w->m_isFloating)\n                        w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position));\n\n                    if (w->isFullscreen()) {\n                        *w->m_realPosition = pMonitor->m_position;\n                        *w->m_realSize     = pMonitor->m_size;\n                    }\n                } else\n                    w->layoutTarget()->setPositionGlobal(CBox{Vector2D{\n                                                                  (pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,\n                                                                  (pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,\n                                                              },\n                                                              w->layoutTarget()->position().size()});\n            }\n\n            w->updateToplevel();\n        }\n    }\n\n    if (SWITCHINGISACTIVE && POLDMON == Desktop::focusState()->monitor()) { // if it was active, preserve its' status. If it wasn't, don't.\n        Log::logger->log(Log::DEBUG, \"moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}\", pMonitor->activeWorkspaceID(), pWorkspace->m_id);\n\n        if (valid(pMonitor->m_activeWorkspace)) {\n            pMonitor->m_activeWorkspace->m_visible = false;\n            g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);\n        }\n\n        if (*PHIDESPECIALONWORKSPACECHANGE)\n            pMonitor->setSpecialWorkspace(nullptr);\n\n        Desktop::focusState()->rawMonitorFocus(pMonitor);\n\n        auto oldWorkspace           = pMonitor->m_activeWorkspace;\n        pMonitor->m_activeWorkspace = pWorkspace;\n\n        if (oldWorkspace)\n            oldWorkspace->m_events.activeChanged.emit();\n\n        pWorkspace->m_events.activeChanged.emit();\n\n        g_layoutManager->recalculateMonitor(pMonitor);\n        g_pHyprRenderer->damageMonitor(pMonitor);\n\n        g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);\n        pWorkspace->m_visible = true;\n\n        if (!noWarpCursor)\n            g_pPointerManager->warpTo(pMonitor->m_position + pMonitor->m_transformedSize / 2.F);\n\n        g_pInputManager->sendMotionEventsToFocused();\n    }\n\n    // finalize\n    if (POLDMON) {\n        g_layoutManager->recalculateMonitor(POLDMON);\n        if (valid(POLDMON->m_activeWorkspace))\n            g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace,\n                                                                   POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :\n                                                                                                                       CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n        updateSuspendedStates();\n    }\n\n    g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n        pWorkspace, pWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n    updateSuspendedStates();\n\n    // event\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"moveworkspace\", .data = pWorkspace->m_name + \",\" + pMonitor->m_name});\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"moveworkspacev2\", .data = std::format(\"{},{},{}\", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)});\n\n    Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor);\n}\n\nbool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) {\n    WORKSPACEID lowestID  = INT64_MAX;\n    WORKSPACEID highestID = INT64_MIN;\n\n    for (auto const& w : getWorkspaces()) {\n        if (w->m_isSpecialWorkspace)\n            continue;\n        lowestID  = std::min(w->m_id, lowestID);\n        highestID = std::max(w->m_id, highestID);\n    }\n\n    return std::clamp(id, lowestID, highestID) != id;\n}\n\nvoid CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON) {\n    setWindowFullscreenClient(\n        PWINDOW,\n        sc<eFullscreenMode>(ON ? sc<uint8_t>(PWINDOW->m_fullscreenState.client) | sc<uint8_t>(MODE) : (sc<uint8_t>(PWINDOW->m_fullscreenState.client) & sc<uint8_t>(~MODE))));\n}\n\n// TODO: move fs functions to Desktop::\nvoid CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) {\n    if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault())\n        setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = MODE, .client = MODE});\n    else\n        setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = MODE, .client = PWINDOW->m_fullscreenState.client});\n}\n\nvoid CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) {\n    if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault())\n        setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = MODE, .client = MODE});\n    else\n        setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = MODE});\n}\n\nvoid CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::View::SFullscreenState state) {\n    static auto PDIRECTSCANOUT      = CConfigValue<Hyprlang::INT>(\"render:direct_scanout\");\n    static auto PALLOWPINFULLSCREEN = CConfigValue<Hyprlang::INT>(\"binds:allow_pin_fullscreen\");\n\n    if (!validMapped(PWINDOW) || g_pCompositor->m_unsafeState)\n        return;\n\n    state.internal = std::clamp(state.internal, sc<eFullscreenMode>(0), FSMODE_MAX);\n    state.client   = std::clamp(state.client, sc<eFullscreenMode>(0), FSMODE_MAX);\n\n    const auto PMONITOR   = PWINDOW->m_monitor.lock();\n    const auto PWORKSPACE = PWINDOW->m_workspace;\n\n    if (PWINDOW->m_isFloating && PWINDOW->m_fullscreenState.internal == FSMODE_NONE && state.internal != FSMODE_NONE)\n        g_pHyprRenderer->damageWindow(PWINDOW);\n\n    if (*PALLOWPINFULLSCREEN && !PWINDOW->m_pinFullscreened && !PWINDOW->isFullscreen() && PWINDOW->m_pinned) {\n        PWINDOW->m_pinned          = false;\n        PWINDOW->m_pinFullscreened = true;\n    }\n\n    if (PWORKSPACE->m_hasFullscreenWindow && !PWINDOW->isFullscreen())\n        setWindowFullscreenInternal(PWORKSPACE->getFullscreenWindow(), FSMODE_NONE);\n\n    const bool CHANGEINTERNAL = !PWINDOW->m_pinned && PWINDOW->m_fullscreenState.internal != state.internal;\n\n    if (*PALLOWPINFULLSCREEN && PWINDOW->m_pinFullscreened && PWINDOW->isFullscreen() && !PWINDOW->m_pinned && state.internal == FSMODE_NONE) {\n        PWINDOW->m_pinned          = true;\n        PWINDOW->m_pinFullscreened = false;\n    }\n\n    // TODO: update the state on syncFullscreen changes\n    if (!CHANGEINTERNAL && PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault())\n        return;\n\n    PWINDOW->m_fullscreenState.client = state.client;\n    g_pXWaylandManager->setWindowFullscreen(PWINDOW, state.client & FSMODE_FULLSCREEN);\n\n    if (!CHANGEINTERNAL) {\n        PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |\n                                                     Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n        PWINDOW->updateDecorationValues();\n        g_layoutManager->recalculateMonitor(PMONITOR);\n        return;\n    }\n\n    // \"Effective mode\" is the fullscreen mode according to which a window is rendered.\n    // For fullscreen modes `FSMODE_NONE` (0), `FSMODE_MAXIMIZED` (1), and `FSMODE_FULLSCREEN` (2),\n    // the effective mode is the same as the fullscreen mode;\n    // for fullscreen mode `FSMODE_MAXIMIZED|FSMODE_FULLSCREEN` (a window is maximized then fullscreened),\n    // the effective mode is `FSMODE_FULLSCREEN` (2), since the window is rendered as a fullscreen window.\n    // But when the latter window exists fullscreen, it will return to `FSMODE_MAXIMIZED`, rather than `FSMODE_NONE`.\n    const eFullscreenMode OLD_EFFECTIVE_MODE = sc<eFullscreenMode>(std::bit_floor(sc<uint8_t>(PWINDOW->m_fullscreenState.internal)));\n    const eFullscreenMode NEW_EFFECTIVE_MODE = sc<eFullscreenMode>(std::bit_floor(sc<uint8_t>(state.internal)));\n\n    PWORKSPACE->m_fullscreenMode      = NEW_EFFECTIVE_MODE;\n    PWORKSPACE->m_hasFullscreenWindow = NEW_EFFECTIVE_MODE != FSMODE_NONE;\n\n    g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), OLD_EFFECTIVE_MODE, NEW_EFFECTIVE_MODE);\n\n    PWINDOW->m_fullscreenState.internal = state.internal;\n\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"fullscreen\", .data = std::to_string(sc<int>(NEW_EFFECTIVE_MODE) != FSMODE_NONE)});\n    Event::bus()->m_events.window.fullscreen.emit(PWINDOW);\n\n    PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |\n                                                 Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n\n    PWINDOW->updateDecorationValues();\n    g_layoutManager->recalculateMonitor(PMONITOR);\n\n    // make all windows and layers on the same workspace under the fullscreen window\n    for (auto const& w : m_windows) {\n        if (w->m_workspace == PWORKSPACE && !w->isFullscreen() && !w->m_fadingOut && !w->m_pinned)\n            w->m_createdOverFullscreen = false;\n    }\n    for (auto const& ls : m_layers) {\n        if (ls->m_monitor == PMONITOR)\n            ls->m_aboveFullscreen = false;\n    }\n\n    g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n        PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n    PWINDOW->sendWindowSize(true);\n\n    PWORKSPACE->forceReportSizesToWindows();\n\n    g_pInputManager->recheckIdleInhibitorStatus();\n\n    // further updates require a monitor\n    if (!PMONITOR)\n        return;\n\n    // send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't.\n    // ignore if DS is disabled.\n    if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) {\n        auto surf = PWINDOW->getSolitaryResource();\n        if (surf)\n            g_pHyprRenderer->setSurfaceScanoutMode(surf, NEW_EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr);\n    }\n\n    g_pConfigManager->ensureVRR(PMONITOR);\n}\n\nPHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) {\n    if (!pWindow->m_isX11)\n        return nullptr;\n\n    for (auto const& w : m_windows) {\n        if (!w->m_isX11)\n            continue;\n\n        if (w->m_xwaylandSurface == pWindow->m_xwaylandSurface->m_parent)\n            return w;\n    }\n\n    return nullptr;\n}\n\nvoid CCompositor::scheduleFrameForMonitor(PHLMONITOR pMonitor, IOutput::scheduleFrameReason reason) {\n    if ((m_aqBackend->hasSession() && !m_aqBackend->session->active) || !m_sessionActive)\n        return;\n\n    if (!pMonitor->m_enabled)\n        return;\n\n    if (pMonitor->m_renderingActive)\n        pMonitor->m_pendingFrame = true;\n\n    pMonitor->m_output->scheduleFrame(reason);\n}\n\nPHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {\n    auto regexp = trim(regexp_);\n\n    if (regexp.starts_with(\"active\"))\n        return Desktop::focusState()->window();\n    else if (regexp.starts_with(\"floating\") || regexp.starts_with(\"tiled\")) {\n        // first floating on the current ws\n        if (!Desktop::focusState()->window())\n            return nullptr;\n\n        const bool FLOAT = regexp.starts_with(\"floating\");\n\n        for (auto const& w : m_windows) {\n            if (!w->m_isMapped || w->m_isFloating != FLOAT || w->m_workspace != Desktop::focusState()->window()->m_workspace || w->isHidden())\n                continue;\n\n            return w;\n        }\n\n        return nullptr;\n    }\n\n    eFocusWindowMode mode = MODE_CLASS_REGEX;\n\n    std::string      regexCheck;\n    std::string      matchCheck;\n    if (regexp.starts_with(\"class:\")) {\n        regexCheck = regexp.substr(6);\n    } else if (regexp.starts_with(\"initialclass:\")) {\n        mode       = MODE_INITIAL_CLASS_REGEX;\n        regexCheck = regexp.substr(13);\n    } else if (regexp.starts_with(\"title:\")) {\n        mode       = MODE_TITLE_REGEX;\n        regexCheck = regexp.substr(6);\n    } else if (regexp.starts_with(\"initialtitle:\")) {\n        mode       = MODE_INITIAL_TITLE_REGEX;\n        regexCheck = regexp.substr(13);\n    } else if (regexp.starts_with(\"tag:\")) {\n        mode       = MODE_TAG_REGEX;\n        regexCheck = regexp.substr(4);\n    } else if (regexp.starts_with(\"address:\")) {\n        mode       = MODE_ADDRESS;\n        matchCheck = regexp.substr(8);\n    } else if (regexp.starts_with(\"pid:\")) {\n        mode       = MODE_PID;\n        matchCheck = regexp.substr(4);\n    }\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!w->m_isMapped)\n            continue;\n\n        switch (mode) {\n            case MODE_CLASS_REGEX: {\n                const auto windowClass = w->m_class;\n                if (!RE2::FullMatch(windowClass, regexCheck))\n                    continue;\n                break;\n            }\n            case MODE_INITIAL_CLASS_REGEX: {\n                const auto initialWindowClass = w->m_initialClass;\n                if (!RE2::FullMatch(initialWindowClass, regexCheck))\n                    continue;\n                break;\n            }\n            case MODE_TITLE_REGEX: {\n                const auto windowTitle = w->m_title;\n                if (!RE2::FullMatch(windowTitle, regexCheck))\n                    continue;\n                break;\n            }\n            case MODE_INITIAL_TITLE_REGEX: {\n                const auto initialWindowTitle = w->m_initialTitle;\n                if (!RE2::FullMatch(initialWindowTitle, regexCheck))\n                    continue;\n                break;\n            }\n            case MODE_TAG_REGEX: {\n                bool tagMatched = false;\n                for (auto const& t : w->m_ruleApplicator->m_tagKeeper.getTags()) {\n                    if (RE2::FullMatch(t, regexCheck)) {\n                        tagMatched = true;\n                        break;\n                    }\n                }\n                if (!tagMatched)\n                    continue;\n                break;\n            }\n            case MODE_ADDRESS: {\n                std::string addr = std::format(\"0x{:x}\", rc<uintptr_t>(w.get()));\n                if (matchCheck != addr)\n                    continue;\n                break;\n            }\n            case MODE_PID: {\n                std::string pid = std::format(\"{}\", w->getPID());\n                if (matchCheck != pid)\n                    continue;\n                break;\n            }\n            default: break;\n        }\n\n        return w;\n    }\n\n    return nullptr;\n}\n\nvoid CCompositor::warpCursorTo(const Vector2D& pos, bool force) {\n\n    // warpCursorTo should only be used for warps that\n    // should be disabled with no_warps\n\n    static auto PNOWARPS = CConfigValue<Hyprlang::INT>(\"cursor:no_warps\");\n\n    if (*PNOWARPS && !force) {\n        const auto PMONITORNEW = getMonitorFromVector(pos);\n        Desktop::focusState()->rawMonitorFocus(PMONITORNEW);\n        return;\n    }\n\n    g_pPointerManager->warpTo(pos);\n\n    const auto PMONITORNEW = getMonitorFromVector(pos);\n    Desktop::focusState()->rawMonitorFocus(PMONITORNEW);\n}\n\nvoid CCompositor::closeWindow(PHLWINDOW pWindow) {\n    if (pWindow && validMapped(pWindow))\n        g_pXWaylandManager->sendCloseWindow(pWindow);\n}\n\nPHLLS CCompositor::getLayerSurfaceFromSurface(SP<CWLSurfaceResource> pSurface) {\n    std::pair<SP<CWLSurfaceResource>, bool> result = {pSurface, false};\n\n    for (auto const& ls : m_layers) {\n        if (!ls->aliveAndVisible())\n            continue;\n\n        if (ls->m_layerSurface->m_surface == pSurface)\n            return ls;\n\n        ls->m_layerSurface->m_surface->breadthfirst(\n            [&result](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) {\n                if (surf == result.first) {\n                    result.second = true;\n                    return;\n                }\n            },\n            nullptr);\n\n        if (result.second)\n            return ls;\n    }\n\n    return nullptr;\n}\n\n// returns a delta\nVector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, const Vector2D& relativeTo) {\n    if (!args.contains(' ') && !args.contains('\\t'))\n        return relativeTo;\n\n    const auto  PMONITOR = Desktop::focusState()->monitor();\n\n    bool        xIsPercent = false;\n    bool        yIsPercent = false;\n    bool        isExact    = false;\n\n    CVarList    varList(args, 0, 's', true);\n    std::string x = varList[0];\n    std::string y = varList[1];\n\n    if (x == \"exact\") {\n        x       = varList[1];\n        y       = varList[2];\n        isExact = true;\n    }\n\n    if (x.contains('%')) {\n        xIsPercent = true;\n        x          = x.substr(0, x.length() - 1);\n    }\n\n    if (y.contains('%')) {\n        yIsPercent = true;\n        y          = y.substr(0, y.length() - 1);\n    }\n\n    if (!isNumber(x) || !isNumber(y)) {\n        Log::logger->log(Log::ERR, \"parseWindowVectorArgsRelative: args not numbers\");\n        return relativeTo;\n    }\n\n    int X = 0;\n    int Y = 0;\n\n    if (isExact) {\n        X = xIsPercent ? std::stof(x) * 0.01 * PMONITOR->m_size.x : std::stoi(x);\n        Y = yIsPercent ? std::stof(y) * 0.01 * PMONITOR->m_size.y : std::stoi(y);\n    } else {\n        X = xIsPercent ? (std::stof(x) * 0.01 * relativeTo.x) + relativeTo.x : std::stoi(x) + relativeTo.x;\n        Y = yIsPercent ? (std::stof(y) * 0.01 * relativeTo.y) + relativeTo.y : std::stoi(y) + relativeTo.y;\n    }\n\n    return Vector2D(X, Y);\n}\n\nPHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITORID& monid, const std::string& name, bool isEmpty) {\n    const auto NAME  = name.empty() ? std::to_string(id) : name;\n    auto       monID = monid;\n\n    // check if bound\n    if (const auto PMONITOR = g_pConfigManager->getBoundMonitorForWS(NAME); PMONITOR)\n        monID = PMONITOR->m_id;\n\n    const bool SPECIAL = id >= SPECIAL_WORKSPACE_START && id <= -2;\n\n    const auto PMONITOR = getMonitorFromID(monID);\n    if (!PMONITOR) {\n        Log::logger->log(Log::ERR, \"BUG THIS: No pMonitor for new workspace in createNewWorkspace\");\n        return nullptr;\n    }\n\n    const auto PWORKSPACE = CWorkspace::create(id, PMONITOR, NAME, SPECIAL, isEmpty);\n\n    PWORKSPACE->m_alpha->setValueAndWarp(0);\n\n    return PWORKSPACE;\n}\n\nbool CCompositor::isWorkspaceSpecial(const WORKSPACEID& id) {\n    return id >= SPECIAL_WORKSPACE_START && id <= -2;\n}\n\nWORKSPACEID CCompositor::getNewSpecialID() {\n    WORKSPACEID highest = SPECIAL_WORKSPACE_START;\n    for (auto const& ws : getWorkspaces()) {\n        if (ws->m_isSpecialWorkspace && ws->m_id > highest)\n            highest = ws->m_id;\n    }\n\n    return highest + 1;\n}\n\nvoid CCompositor::registerWorkspace(PHLWORKSPACE w) {\n    m_workspaces.emplace_back(w);\n    w->m_events.destroy.listenStatic([this, weak = PHLWORKSPACEREF{w}] { std::erase(m_workspaces, weak); });\n}\n\nstd::vector<PHLWORKSPACE> CCompositor::getWorkspacesCopy() {\n    std::vector<PHLWORKSPACE> wsp;\n    auto                      range = getWorkspaces();\n    wsp.reserve(std::ranges::distance(range));\n    for (auto& r : range) {\n        wsp.emplace_back(r.lock());\n    }\n    return wsp;\n}\n\nvoid CCompositor::performUserChecks() {\n    static auto PNOCHECKXDG      = CConfigValue<Hyprlang::INT>(\"misc:disable_xdg_env_checks\");\n    static auto PNOCHECKGUIUTILS = CConfigValue<Hyprlang::INT>(\"misc:disable_hyprland_guiutils_check\");\n    static auto PNOWATCHDOG      = CConfigValue<Hyprlang::INT>(\"misc:disable_watchdog_warning\");\n\n    if (!*PNOCHECKXDG) {\n        const auto CURRENT_DESKTOP_ENV = getenv(\"XDG_CURRENT_DESKTOP\");\n        if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != \"Hyprland\") {\n            g_pHyprNotificationOverlay->addNotification(\n                I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, {{\"value\", CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : \"unset\"}}), CHyprColor{}, 15000,\n                ICON_WARNING);\n        }\n    }\n\n    if (!*PNOCHECKGUIUTILS) {\n        if (!NFsUtils::executableExistsInPath(\"hyprland-dialog\"))\n            g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING);\n    }\n\n    if (g_pHyprRenderer->m_failedAssetsNo > 0) {\n        g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{\"count\", std::to_string(g_pHyprRenderer->m_failedAssetsNo)}}),\n                                                    CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR);\n    }\n\n    if (!m_watchdogWriteFd.isValid() && !*PNOWATCHDOG)\n        g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_WATCHDOG), CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_WARNING);\n\n    if (m_safeMode)\n        openSafeModeBox();\n}\n\nvoid CCompositor::openSafeModeBox() {\n    const auto OPT_LOAD = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG);\n    const auto OPT_OPEN = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR);\n    const auto OPT_OK   = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD);\n\n    auto       box = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_TITLE), I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_DESCRIPTION),\n                                             {\n                                                 OPT_LOAD,\n                                                 OPT_OPEN,\n                                                 OPT_OK,\n                                             });\n\n    box->open()->then([OPT_LOAD, OPT_OK, OPT_OPEN, this](SP<CPromiseResult<std::string>> result) {\n        if (result->hasError())\n            return;\n\n        const auto RES = result->result();\n\n        if (RES.starts_with(OPT_LOAD)) {\n            m_safeMode = false;\n            g_pConfigManager->reload();\n        } else if (RES.starts_with(OPT_OPEN)) {\n            std::string reportPath;\n            const auto  HOME       = getenv(\"HOME\");\n            const auto  CACHE_HOME = getenv(\"XDG_CACHE_HOME\");\n\n            if (CACHE_HOME && CACHE_HOME[0] != '\\0') {\n                reportPath += CACHE_HOME;\n                reportPath += \"/hyprland/\";\n            } else if (HOME && HOME[0] != '\\0') {\n                reportPath += HOME;\n                reportPath += \"/.cache/hyprland/\";\n            }\n            Hyprutils::OS::CProcess proc(\"xdg-open\", {reportPath});\n\n            proc.runAsync();\n\n            // reopen\n            openSafeModeBox();\n        }\n    });\n}\n\nvoid CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) {\n    if (!pWindow || !pWorkspace)\n        return;\n\n    if (pWindow->m_pinned && pWorkspace->m_isSpecialWorkspace)\n        return;\n\n    if (pWindow->m_workspace == pWorkspace)\n        return;\n\n    const bool FULLSCREEN     = pWindow->isFullscreen();\n    const auto FULLSCREENMODE = pWindow->m_fullscreenState.internal;\n    const bool WASVISIBLE     = pWindow->m_workspace && pWindow->m_workspace->isVisible();\n\n    if (FULLSCREEN)\n        setWindowFullscreenInternal(pWindow, FSMODE_NONE);\n\n    const PHLWINDOW pFirstWindowOnWorkspace   = pWorkspace->getFirstWindow();\n    const int       visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true);\n    const auto      POSTOMON                  = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{});\n    const auto      PWORKSPACEMONITOR         = pWorkspace->m_monitor.lock();\n\n    pWindow->moveToWorkspace(pWorkspace);\n    pWindow->m_monitor = pWorkspace->m_monitor;\n\n    static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>(\"group:group_on_movetoworkspace\");\n    if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group &&\n        pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) {\n        pFirstWindowOnWorkspace->m_group->add(pWindow);\n    } else {\n        if (pWindow->m_isFloating)\n            pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()});\n    }\n\n    pWindow->updateToplevel();\n    pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n    pWindow->uncacheWindowDecos();\n\n    if (pWindow->m_group)\n        pWindow->m_group->updateWorkspace(pWorkspace);\n\n    g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space);\n\n    if (FULLSCREEN)\n        setWindowFullscreenInternal(pWindow, FULLSCREENMODE);\n\n    pWorkspace->updateWindows();\n    if (pWindow->m_workspace)\n        pWindow->m_workspace->updateWindows();\n    g_pCompositor->updateSuspendedStates();\n\n    if (!WASVISIBLE && pWindow->m_workspace && pWindow->m_workspace->isVisible()) {\n        pWindow->m_movingFromWorkspaceAlpha->setValueAndWarp(0.F);\n        *pWindow->m_movingFromWorkspaceAlpha = 1.F;\n    }\n}\n\nPHLWINDOW CCompositor::getForceFocus() {\n    for (auto const& w : m_windows) {\n        if (!w->m_isMapped || w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible())\n            continue;\n\n        if (!w->m_ruleApplicator->stayFocused().valueOrDefault())\n            continue;\n\n        return w;\n    }\n\n    return nullptr;\n}\n\nvoid CCompositor::scheduleMonitorStateRecheck() {\n    static bool scheduled = false;\n\n    if (!scheduled) {\n        scheduled = true;\n        g_pEventLoopManager->doLater([this] {\n            arrangeMonitors();\n            checkMonitorOverlaps();\n\n            scheduled = false;\n        });\n    }\n}\n\nvoid CCompositor::checkMonitorOverlaps() {\n    CRegion monitorRegion;\n\n    for (const auto& m : m_monitors) {\n        if (!monitorRegion.copy().intersect(m->logicalBox()).empty()) {\n            Log::logger->log(Log::ERR, \"Monitor {}: detected overlap with layout\", m->m_name);\n            g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{\"name\", m->m_name}}), CHyprColor{}, 15000,\n                                                        ICON_WARNING);\n\n            break;\n        }\n\n        monitorRegion.add(m->logicalBox());\n    }\n}\n\nvoid CCompositor::arrangeMonitors() {\n    static auto* const      PXWLFORCESCALEZERO = rc<Hyprlang::INT* const*>(g_pConfigManager->getConfigValuePtr(\"xwayland:force_zero_scaling\"));\n\n    std::vector<PHLMONITOR> toArrange(m_monitors.begin(), m_monitors.end());\n    std::vector<PHLMONITOR> arranged;\n    arranged.reserve(toArrange.size());\n\n    Log::logger->log(Log::DEBUG, \"arrangeMonitors: {} to arrange\", toArrange.size());\n\n    for (auto it = toArrange.begin(); it != toArrange.end();) {\n        auto m = *it;\n\n        if (m->m_activeMonitorRule.offset != Vector2D{-INT32_MAX, -INT32_MAX}) {\n            // explicit.\n            Log::logger->log(Log::DEBUG, \"arrangeMonitors: {} explicit {:j}\", m->m_name, m->m_activeMonitorRule.offset);\n\n            m->moveTo(m->m_activeMonitorRule.offset);\n            arranged.push_back(m);\n            it = toArrange.erase(it);\n\n            if (it == toArrange.end())\n                break;\n\n            continue;\n        }\n\n        ++it;\n    }\n\n    // Variables to store the max and min values of monitors on each axis.\n    int  maxXOffsetRight = 0;\n    int  maxXOffsetLeft  = 0;\n    int  maxYOffsetUp    = 0;\n    int  maxYOffsetDown  = 0;\n\n    auto recalcMaxOffsets = [&]() {\n        maxXOffsetRight = 0;\n        maxXOffsetLeft  = 0;\n        maxYOffsetUp    = 0;\n        maxYOffsetDown  = 0;\n\n        // Finds the max and min values of explicitly placed monitors.\n        for (auto const& m : arranged) {\n            maxXOffsetRight = std::max<double>(m->m_position.x + m->m_size.x, maxXOffsetRight);\n            maxXOffsetLeft  = std::min<double>(m->m_position.x, maxXOffsetLeft);\n            maxYOffsetDown  = std::max<double>(m->m_position.y + m->m_size.y, maxYOffsetDown);\n            maxYOffsetUp    = std::min<double>(m->m_position.y, maxYOffsetUp);\n        }\n    };\n\n    // Iterates through all non-explicitly placed monitors.\n    for (auto const& m : toArrange) {\n        recalcMaxOffsets();\n\n        // Moves the monitor to their appropriate position on the x/y axis and\n        // increments/decrements the corresponding max offset.\n        Vector2D newPosition = {0, 0};\n        switch (m->m_activeMonitorRule.autoDir) {\n            case eAutoDirs::DIR_AUTO_UP: newPosition.y = maxYOffsetUp - m->m_size.y; break;\n            case eAutoDirs::DIR_AUTO_DOWN: newPosition.y = maxYOffsetDown; break;\n            case eAutoDirs::DIR_AUTO_LEFT: newPosition.x = maxXOffsetLeft - m->m_size.x; break;\n            case eAutoDirs::DIR_AUTO_RIGHT:\n            case eAutoDirs::DIR_AUTO_NONE: newPosition.x = maxXOffsetRight; break;\n            case eAutoDirs::DIR_AUTO_CENTER_UP: {\n                int width     = maxXOffsetRight - maxXOffsetLeft;\n                newPosition.y = maxYOffsetUp - m->m_size.y;\n                newPosition.x = maxXOffsetLeft + (width - m->m_size.x) / 2;\n                break;\n            }\n            case eAutoDirs::DIR_AUTO_CENTER_DOWN: {\n                int width     = maxXOffsetRight - maxXOffsetLeft;\n                newPosition.y = maxYOffsetDown;\n                newPosition.x = maxXOffsetLeft + (width - m->m_size.x) / 2;\n                break;\n            }\n            case eAutoDirs::DIR_AUTO_CENTER_LEFT: {\n                int height    = maxYOffsetDown - maxYOffsetUp;\n                newPosition.x = maxXOffsetLeft - m->m_size.x;\n                newPosition.y = maxYOffsetUp + (height - m->m_size.y) / 2;\n                break;\n            }\n            case eAutoDirs::DIR_AUTO_CENTER_RIGHT: {\n                int height    = maxYOffsetDown - maxYOffsetUp;\n                newPosition.x = maxXOffsetRight;\n                newPosition.y = maxYOffsetUp + (height - m->m_size.y) / 2;\n                break;\n            }\n            default: UNREACHABLE();\n        }\n        Log::logger->log(Log::DEBUG, \"arrangeMonitors: {} auto {:j}\", m->m_name, m->m_position);\n        m->moveTo(newPosition);\n        arranged.emplace_back(m);\n    }\n\n    // reset maxXOffsetRight (reuse)\n    // and set xwayland positions aka auto for all\n    maxXOffsetRight = 0;\n    for (auto const& m : m_monitors) {\n        Log::logger->log(Log::DEBUG, \"arrangeMonitors: {} xwayland [{}, {}]\", m->m_name, maxXOffsetRight, 0);\n        m->m_xwaylandPosition = {maxXOffsetRight, 0};\n        maxXOffsetRight += (*PXWLFORCESCALEZERO ? m->m_transformedSize.x : m->m_size.x);\n\n        if (*PXWLFORCESCALEZERO)\n            m->m_xwaylandScale = m->m_scale;\n        else\n            m->m_xwaylandScale = 1.f;\n    }\n\n    PROTO::xdgOutput->updateAllOutputs();\n    Event::bus()->m_events.monitor.layoutChanged.emit();\n\n#ifndef NO_XWAYLAND\n    const auto box = g_pCompositor->calculateX11WorkArea();\n    if (g_pXWayland && g_pXWayland->m_wm) {\n        if (box)\n            g_pXWayland->m_wm->updateWorkArea(box->x, box->y, box->w, box->h);\n        else\n            g_pXWayland->m_wm->updateWorkArea(0, 0, 0, 0);\n    }\n\n#endif\n}\n\nvoid CCompositor::enterUnsafeState() {\n    if (m_unsafeState)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"Entering unsafe state\");\n\n    if (!m_unsafeOutput->m_enabled)\n        m_unsafeOutput->onConnect(false);\n\n    m_unsafeState = true;\n\n    Desktop::focusState()->rawMonitorFocus(m_unsafeOutput.lock());\n}\n\nvoid CCompositor::leaveUnsafeState() {\n    if (!m_unsafeState)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"Leaving unsafe state\");\n\n    m_unsafeState = false;\n\n    PHLMONITOR pNewMonitor = nullptr;\n    for (auto const& pMonitor : m_monitors) {\n        if (pMonitor->m_output != m_unsafeOutput->m_output) {\n            pNewMonitor = pMonitor;\n            break;\n        }\n    }\n\n    RASSERT(pNewMonitor, \"Tried to leave unsafe without a monitor\");\n\n    if (m_unsafeOutput->m_enabled)\n        m_unsafeOutput->onDisconnect();\n\n    for (auto const& m : m_monitors) {\n        scheduleFrameForMonitor(m);\n    }\n}\n\nvoid CCompositor::setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale) {\n    PROTO::fractional->sendScale(pSurface, scale);\n    pSurface->sendPreferredScale(std::ceil(scale));\n\n    const auto PSURFACE = Desktop::View::CWLSurface::fromResource(pSurface);\n    if (!PSURFACE) {\n        Log::logger->log(Log::WARN, \"Orphaned CWLSurfaceResource {:x} in setPreferredScaleForSurface\", rc<uintptr_t>(pSurface.get()));\n        return;\n    }\n\n    PSURFACE->m_lastScaleFloat = scale;\n    PSURFACE->m_lastScaleInt   = sc<int32_t>(std::ceil(scale));\n}\n\nvoid CCompositor::setPreferredTransformForSurface(SP<CWLSurfaceResource> pSurface, wl_output_transform transform) {\n    pSurface->sendPreferredTransform(transform);\n\n    const auto PSURFACE = Desktop::View::CWLSurface::fromResource(pSurface);\n    if (!PSURFACE) {\n        Log::logger->log(Log::WARN, \"Orphaned CWLSurfaceResource {:x} in setPreferredTransformForSurface\", rc<uintptr_t>(pSurface.get()));\n        return;\n    }\n\n    PSURFACE->m_lastTransform = transform;\n}\n\nvoid CCompositor::updateSuspendedStates() {\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!w->m_isMapped)\n            continue;\n\n        w->setSuspended(w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible());\n    }\n}\n\nstatic void checkDefaultCursorWarp(PHLMONITOR monitor) {\n    static auto PCURSORMONITOR    = CConfigValue<std::string>(\"cursor:default_monitor\");\n    static bool cursorDefaultDone = false;\n    static bool firstLaunch       = true;\n\n    const auto  POS = monitor->middle();\n\n    // by default, cursor should be set to first monitor detected\n    // this is needed as a default if the monitor given in config above doesn't exist\n    if (firstLaunch) {\n        firstLaunch = false;\n        g_pCompositor->warpCursorTo(POS, true);\n        g_pInputManager->refocus();\n        return;\n    }\n\n    if (!cursorDefaultDone && *PCURSORMONITOR != STRVAL_EMPTY) {\n        if (*PCURSORMONITOR == monitor->m_name) {\n            cursorDefaultDone = true;\n            g_pCompositor->warpCursorTo(POS, true);\n            g_pInputManager->refocus();\n            return;\n        }\n    }\n\n    // modechange happened check if cursor is on that monitor and warp it to middle to not place it out of bounds if resolution changed.\n    if (g_pCompositor->getMonitorFromCursor() == monitor) {\n        g_pCompositor->warpCursorTo(POS, true);\n        g_pInputManager->refocus();\n    }\n}\n\nvoid CCompositor::onNewMonitor(SP<Aquamarine::IOutput> output) {\n    // add it to real\n    auto PNEWMONITOR = g_pCompositor->m_realMonitors.emplace_back(makeShared<CMonitor>(output));\n    if (std::string(\"HEADLESS-1\") == output->name) {\n        g_pCompositor->m_unsafeOutput = PNEWMONITOR;\n        output->name                  = \"FALLBACK\"; // we are allowed to do this :)\n    }\n\n    Log::logger->log(Log::DEBUG, \"New output with name {}\", output->name);\n\n    PNEWMONITOR->m_name             = output->name;\n    PNEWMONITOR->m_self             = PNEWMONITOR;\n    const bool FALLBACK             = g_pCompositor->m_unsafeOutput ? output == g_pCompositor->m_unsafeOutput->m_output : false;\n    PNEWMONITOR->m_id               = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name);\n    PNEWMONITOR->m_isUnsafeFallback = FALLBACK;\n\n    Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR);\n\n    if (!FALLBACK)\n        PNEWMONITOR->onConnect(false);\n\n    if (!PNEWMONITOR->m_enabled || FALLBACK)\n        return;\n\n    // ready to process if we have a real monitor\n\n    if ((!g_pHyprRenderer->m_mostHzMonitor || PNEWMONITOR->m_refreshRate > g_pHyprRenderer->m_mostHzMonitor->m_refreshRate) && PNEWMONITOR->m_enabled)\n        g_pHyprRenderer->m_mostHzMonitor = PNEWMONITOR;\n\n    g_pCompositor->m_readyToProcess = true;\n\n    g_pConfigManager->m_wantsMonitorReload = true;\n    g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR, IOutput::AQ_SCHEDULE_NEW_MONITOR);\n\n    checkDefaultCursorWarp(PNEWMONITOR);\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_monitor == PNEWMONITOR) {\n            w->m_lastSurfaceMonitorID = MONITOR_INVALID;\n            w->updateSurfaceScaleTransformDetails();\n        }\n    }\n\n    g_pHyprRenderer->damageMonitor(PNEWMONITOR);\n    PNEWMONITOR->m_frameScheduler->onFrame();\n\n    if (PROTO::colorManagement && shouldChangePreferredImageDescription()) {\n        Log::logger->log(Log::ERR, \"FIXME: color management protocol is enabled, need a preferred image description id\");\n        PROTO::colorManagement->onImagePreferredChanged(0);\n    }\n}\n\nPImageDescription CCompositor::getPreferredImageDescription() {\n    if (!PROTO::colorManagement) {\n        Log::logger->log(Log::ERR, \"FIXME: color management protocol is not enabled, returning empty image description\");\n        return DEFAULT_IMAGE_DESCRIPTION;\n    }\n    Log::logger->log(Log::WARN, \"FIXME: color management protocol is enabled, determine correct preferred image description\");\n    // should determine some common settings to avoid unnecessary transformations while keeping maximum displayable precision\n    return m_monitors.size() == 1 ? m_monitors[0]->m_imageDescription : CImageDescription::from(SImageDescription{.primaries = NColorPrimaries::BT709});\n}\n\nPImageDescription CCompositor::getHDRImageDescription() {\n    if (!PROTO::colorManagement) {\n        Log::logger->log(Log::ERR, \"FIXME: color management protocol is not enabled, returning empty image description\");\n        return DEFAULT_IMAGE_DESCRIPTION;\n    }\n\n    return m_monitors.size() == 1 && m_monitors[0]->m_output && m_monitors[0]->m_output->parsedEDID.hdrMetadata.has_value() ?\n        CImageDescription::from(SImageDescription{\n            .transferFunction    = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,\n            .primariesNameSet    = true,\n            .primariesNamed      = NColorManagement::CM_PRIMARIES_BT2020,\n            .primaries           = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),\n            .masteringPrimaries  = m_monitors[0]->getMasteringPrimaries(),\n            .luminances          = {.min = m_monitors[0]->minLuminance(HDR_MIN_LUMINANCE), .max = m_monitors[0]->maxLuminance(HDR_MAX_LUMINANCE), .reference = HDR_REF_LUMINANCE},\n            .masteringLuminances = m_monitors[0]->getMasteringLuminances(),\n            .maxCLL              = m_monitors[0]->maxCLL(),\n            .maxFALL             = m_monitors[0]->maxFALL()}) :\n        DEFAULT_HDR_IMAGE_DESCRIPTION;\n}\n\nbool CCompositor::shouldChangePreferredImageDescription() {\n    Log::logger->log(Log::WARN, \"FIXME: color management protocol is enabled and outputs changed, check preferred image description changes\");\n    return false;\n}\n\nvoid CCompositor::ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace) {\n    if (!Desktop::focusState()->monitor())\n        return;\n\n    std::vector<PHLWORKSPACE> persistentFound;\n\n    for (const auto& rule : rules) {\n        if (!rule.isPersistent)\n            continue;\n\n        PHLWORKSPACE PWORKSPACE = nullptr;\n        if (pWorkspace) {\n            if (pWorkspace->matchesStaticSelector(rule.workspaceString))\n                PWORKSPACE = pWorkspace;\n            else\n                continue;\n        }\n\n        auto PMONITOR = getMonitorFromString(rule.monitor);\n\n        if (!rule.monitor.empty() && !PMONITOR)\n            continue; // don't do anything yet, as the monitor is not yet present.\n\n        if (!PWORKSPACE) {\n            WORKSPACEID id     = rule.workspaceId;\n            std::string wsname = rule.workspaceName;\n\n            if (id == WORKSPACE_INVALID) {\n                const auto R = getWorkspaceIDNameFromString(rule.workspaceString);\n                id           = R.id;\n                wsname       = R.name;\n            }\n\n            if (id == WORKSPACE_INVALID) {\n                Log::logger->log(Log::ERR, \"ensurePersistentWorkspacesPresent: couldn't resolve id for workspace {}\", rule.workspaceString);\n                continue;\n            }\n            PWORKSPACE = getWorkspaceByID(id);\n            if (!PMONITOR)\n                PMONITOR = Desktop::focusState()->monitor();\n\n            if (!PWORKSPACE)\n                PWORKSPACE = createNewWorkspace(id, PMONITOR->m_id, wsname, false);\n        }\n\n        if (!PMONITOR) {\n            Log::logger->log(Log::ERR, \"ensurePersistentWorkspacesPresent: couldn't resolve monitor for {}, skipping\", rule.monitor);\n            continue;\n        }\n\n        if (PWORKSPACE)\n            PWORKSPACE->setPersistent(true);\n\n        if (!pWorkspace)\n            persistentFound.emplace_back(PWORKSPACE);\n\n        if (PWORKSPACE) {\n            if (PWORKSPACE->m_monitor == PMONITOR) {\n                Log::logger->log(Log::DEBUG, \"ensurePersistentWorkspacesPresent: workspace persistent {} already on {}\", rule.workspaceString, PMONITOR->m_name);\n\n                continue;\n            }\n\n            Log::logger->log(Log::DEBUG, \"ensurePersistentWorkspacesPresent: workspace persistent {} not on {}, moving\", rule.workspaceString, PMONITOR->m_name);\n            moveWorkspaceToMonitor(PWORKSPACE, PMONITOR);\n            continue;\n        }\n    }\n\n    if (!pWorkspace) {\n        // check non-persistent and downgrade if workspace is no longer persistent\n        std::vector<PHLWORKSPACEREF> toDowngrade;\n        for (auto& w : getWorkspaces()) {\n            if (!w->isPersistent())\n                continue;\n\n            if (std::ranges::contains(persistentFound, w.lock()))\n                continue;\n\n            toDowngrade.emplace_back(w);\n        }\n\n        for (auto& ws : toDowngrade) {\n            ws->setPersistent(false);\n        }\n    }\n}\n\nvoid CCompositor::ensureWorkspacesOnAssignedMonitors() {\n    for (auto const& ws : getWorkspacesCopy()) {\n        if (!valid(ws) || ws->m_isSpecialWorkspace)\n            continue;\n\n        const auto RULE = g_pConfigManager->getWorkspaceRuleFor(ws);\n        if (RULE.monitor.empty())\n            continue;\n\n        const auto PMONITOR = getMonitorFromString(RULE.monitor);\n        if (!PMONITOR)\n            continue;\n\n        if (ws->m_monitor == PMONITOR)\n            continue;\n\n        Log::logger->log(Log::DEBUG, \"ensureWorkspacesOnAssignedMonitors: moving workspace {} to {}\", ws->m_name, PMONITOR->m_name);\n        moveWorkspaceToMonitor(ws, PMONITOR, true);\n    }\n}\n\nstd::optional<unsigned int> CCompositor::getVTNr() {\n    if (!m_aqBackend->hasSession())\n        return std::nullopt;\n\n    unsigned int                   ttynum = 0;\n    Hyprutils::OS::CFileDescriptor fd{open(\"/dev/tty\", O_RDONLY | O_NOCTTY)};\n    if (fd.isValid()) {\n#if defined(VT_GETSTATE)\n        struct vt_stat st;\n        if (!ioctl(fd.get(), VT_GETSTATE, &st))\n            ttynum = st.v_active;\n#elif defined(VT_GETACTIVE)\n        int vt;\n        if (!ioctl(fd.get(), VT_GETACTIVE, &vt))\n            ttynum = vt;\n#endif\n    }\n\n    return ttynum;\n}\n\nbool CCompositor::isVRRActiveOnAnyMonitor() const {\n    return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; });\n}\n"
  },
  {
    "path": "src/Compositor.hpp",
    "content": "#pragma once\n\n#include <sys/resource.h>\n\n#include <ranges>\n\n#include \"helpers/math/Direction.hpp\"\n#include \"managers/XWaylandManager.hpp\"\n#include \"managers/KeybindManager.hpp\"\n#include \"managers/SessionLockManager.hpp\"\n#include \"desktop/view/Window.hpp\"\n#include \"helpers/cm/ColorManagement.hpp\"\n\n#include <aquamarine/backend/Backend.hpp>\n#include <aquamarine/output/Output.hpp>\n\nclass CWLSurfaceResource;\nstruct SWorkspaceRule;\n\nenum eManagersInitStage : uint8_t {\n    STAGE_PRIORITY = 0,\n    STAGE_BASICINIT,\n    STAGE_LATE\n};\n\nclass CCompositor {\n  public:\n    CCompositor(bool onlyConfig = false);\n    ~CCompositor();\n\n    wl_display*    m_wlDisplay   = nullptr;\n    wl_event_loop* m_wlEventLoop = nullptr;\n    struct {\n        int  fd             = -1;\n        bool syncobjSupport = false;\n    } m_drm;\n\n    struct {\n        int  fd             = -1;\n        bool syncObjSupport = false;\n    } m_drmRenderNode;\n\n    bool                                         m_initialized = false;\n    bool                                         m_safeMode    = false;\n    SP<Aquamarine::CBackend>                     m_aqBackend;\n\n    std::string                                  m_hyprTempDataRoot = \"\";\n\n    std::string                                  m_wlDisplaySocket   = \"\";\n    std::string                                  m_instanceSignature = \"\";\n    std::string                                  m_instancePath      = \"\";\n    std::string                                  m_currentSplash     = \"error\";\n\n    std::vector<PHLMONITOR>                      m_monitors;\n    std::vector<PHLMONITOR>                      m_realMonitors; // for all monitors, even those turned off\n    std::vector<PHLWINDOW>                       m_windows;\n    std::vector<PHLLS>                           m_layers;\n    std::vector<PHLWINDOWREF>                    m_windowsFadingOut;\n    std::vector<PHLLSREF>                        m_surfacesFadingOut;\n    std::vector<SP<Desktop::View::IView>>        m_otherViews;\n\n    std::unordered_map<std::string, MONITORID>   m_monitorIDMap;\n    std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs\n\n    void                                         initServer(std::string socketName, int socketFd);\n    void                                         startCompositor();\n    void                                         stopCompositor();\n    void                                         cleanup();\n    void                                         bumpNofile();\n    void                                         restoreNofile();\n    bool                                         setWatchdogFd(int fd);\n\n    bool                                         m_readyToProcess = false;\n    bool                                         m_sessionActive  = true;\n    bool                                         m_dpmsStateOn    = true;\n    bool                                         m_unsafeState    = false; // unsafe state is when there is no monitors\n    PHLMONITORREF                                m_unsafeOutput;           // fallback output for the unsafe state\n    bool                                         m_isShuttingDown         = false;\n    bool                                         m_finalRequests          = false;\n    bool                                         m_desktopEnvSet          = false;\n    bool                                         m_wantsXwayland          = true;\n    bool                                         m_onlyConfigVerification = false;\n\n    // ------------------------------------------------- //\n\n    auto getWorkspaces() {\n        return std::views::filter(m_workspaces, [](const auto& e) { return e; });\n    }\n    std::vector<PHLWORKSPACE> getWorkspacesCopy();\n    void                      registerWorkspace(PHLWORKSPACE w);\n\n    //\n\n    PHLMONITOR             getMonitorFromID(const MONITORID&);\n    PHLMONITOR             getMonitorFromName(const std::string&);\n    PHLMONITOR             getMonitorFromDesc(const std::string&);\n    PHLMONITOR             getMonitorFromCursor();\n    PHLMONITOR             getMonitorFromVector(const Vector2D&);\n    void                   removeWindowFromVectorSafe(PHLWINDOW);\n    bool                   monitorExists(PHLMONITOR);\n    PHLWINDOW              vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr);\n    SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);\n    SP<CWLSurfaceResource> vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*);\n    SP<CWLSurfaceResource> vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl);\n    Vector2D               vectorToSurfaceLocal(const Vector2D&, PHLWINDOW, SP<CWLSurfaceResource>);\n    PHLMONITOR             getMonitorFromOutput(SP<Aquamarine::IOutput>);\n    PHLMONITOR             getRealMonitorFromOutput(SP<Aquamarine::IOutput>);\n    PHLWINDOW              getWindowFromSurface(SP<CWLSurfaceResource>);\n    PHLWINDOW              getWindowFromHandle(uint32_t);\n    PHLWORKSPACE           getWorkspaceByID(const WORKSPACEID&);\n    PHLWORKSPACE           getWorkspaceByName(const std::string&);\n    PHLWORKSPACE           getWorkspaceByString(const std::string&);\n    PHLWINDOW              getUrgentWindow();\n    bool                   isWindowActive(PHLWINDOW);\n    void                   changeWindowZOrder(PHLWINDOW, bool);\n    void                   cleanupFadingOut(const MONITORID& monid);\n    PHLWINDOW              getWindowInDirection(PHLWINDOW, Math::eDirection);\n    PHLWINDOW              getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);\n    PHLWINDOW              getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false);\n    PHLWINDOW              getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);\n    WORKSPACEID            getNextAvailableNamedWorkspace();\n    bool                   isPointOnAnyMonitor(const Vector2D&);\n    bool                   isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);\n    std::optional<CBox>    calculateX11WorkArea();\n    PHLMONITOR             getMonitorInDirection(Math::eDirection);\n    PHLMONITOR             getMonitorInDirection(PHLMONITOR, Math::eDirection);\n    void                   updateAllWindowsAnimatedDecorationValues();\n    MONITORID              getNextAvailableMonitorID(std::string const& name);\n    void                   moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);\n    void                   swapActiveWorkspaces(PHLMONITOR, PHLMONITOR);\n    PHLMONITOR             getMonitorFromString(const std::string&);\n    bool                   workspaceIDOutOfBounds(const WORKSPACEID&);\n    void                   setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);\n    void                   setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);\n    void                   setWindowFullscreenState(const PHLWINDOW PWINDOW, const Desktop::View::SFullscreenState state);\n    void                   changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);\n    PHLWINDOW              getX11Parent(PHLWINDOW);\n    void                   scheduleFrameForMonitor(PHLMONITOR, Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN);\n    void                   addToFadingOutSafe(PHLLS);\n    void                   removeFromFadingOutSafe(PHLLS);\n    void                   addToFadingOutSafe(PHLWINDOW);\n    PHLWINDOW              getWindowByRegex(const std::string&);\n    void                   warpCursorTo(const Vector2D&, bool force = false);\n    PHLLS                  getLayerSurfaceFromSurface(SP<CWLSurfaceResource>);\n    void                   closeWindow(PHLWINDOW);\n    Vector2D               parseWindowVectorArgsRelative(const std::string&, const Vector2D&);\n    [[nodiscard]] PHLWORKSPACE          createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = \"\",\n                                                           bool isEmpty = true); // will be deleted next frame if left empty and unfocused!\n    bool                                isWorkspaceSpecial(const WORKSPACEID&);\n    WORKSPACEID                         getNewSpecialID();\n    void                                performUserChecks();\n    void                                moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace);\n    PHLWINDOW                           getForceFocus();\n    void                                scheduleMonitorStateRecheck();\n    void                                arrangeMonitors();\n    void                                checkMonitorOverlaps();\n    void                                enterUnsafeState();\n    void                                leaveUnsafeState();\n    void                                setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale);\n    void                                setPreferredTransformForSurface(SP<CWLSurfaceResource> pSurface, wl_output_transform transform);\n    void                                updateSuspendedStates();\n    void                                onNewMonitor(SP<Aquamarine::IOutput> output);\n    void                                ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);\n    void                                ensureWorkspacesOnAssignedMonitors();\n    std::optional<unsigned int>         getVTNr();\n    bool                                isVRRActiveOnAnyMonitor() const;\n\n    NColorManagement::PImageDescription getPreferredImageDescription();\n    NColorManagement::PImageDescription getHDRImageDescription();\n    bool                                shouldChangePreferredImageDescription();\n\n    bool                                supportsDrmSyncobjTimeline() const;\n    std::string                         m_explicitConfigPath;\n\n  private:\n    void                           initAllSignals();\n    void                           removeAllSignals();\n    void                           cleanEnvironment();\n    void                           setRandomSplash();\n    void                           initManagers(eManagersInitStage stage);\n    void                           prepareFallbackOutput();\n    void                           createLockFile();\n    void                           removeLockFile();\n    void                           setMallocThreshold();\n    void                           openSafeModeBox();\n\n    uint64_t                       m_hyprlandPID    = 0;\n    wl_event_source*               m_critSigSource  = nullptr;\n    rlimit                         m_originalNofile = {};\n    Hyprutils::OS::CFileDescriptor m_watchdogWriteFd;\n\n    std::vector<PHLWORKSPACEREF>   m_workspaces;\n};\n\ninline UP<CCompositor> g_pCompositor;\n"
  },
  {
    "path": "src/SharedDefs.hpp",
    "content": "#pragma once\n\n#include \"helpers/math/Math.hpp\"\n#include <functional>\n#include <any>\n#include <string>\n#include <algorithm>\n#include <hyprutils/math/Box.hpp>\n\nenum eIcons : uint8_t {\n    ICON_WARNING = 0,\n    ICON_INFO,\n    ICON_HINT,\n    ICON_ERROR,\n    ICON_CONFUSED,\n    ICON_OK,\n    ICON_NONE\n};\n\nenum eRenderStage : uint8_t {\n    RENDER_PRE = 0,        /* Before binding the gl context */\n    RENDER_BEGIN,          /* Just when the rendering begins, nothing has been rendered yet. Damage, current render data in opengl valid. */\n    RENDER_POST_WALLPAPER, /* After background layer, but before bottom and overlay layers */\n    RENDER_PRE_WINDOWS,    /* Pre windows, post bottom and overlay layers */\n    RENDER_POST_WINDOWS,   /* Post windows, pre top/overlay layers, etc */\n    RENDER_LAST_MOMENT,    /* Last moment to render with the gl context */\n    RENDER_POST,           /* After rendering is finished, gl context not available anymore */\n    RENDER_POST_MIRROR,    /* After rendering a mirror */\n    RENDER_PRE_WINDOW,     /* Before rendering a window (any pass) Note some windows (e.g. tiled) may have 2 passes (main & popup) */\n    RENDER_POST_WINDOW,    /* After rendering a window (any pass) */\n};\n\nenum eInputType : uint8_t {\n    INPUT_TYPE_AXIS = 0,\n    INPUT_TYPE_BUTTON,\n    INPUT_TYPE_DRAG_START,\n    INPUT_TYPE_DRAG_END,\n    INPUT_TYPE_MOTION\n};\n\nenum eHyprCtlOutputFormat : uint8_t {\n    FORMAT_NORMAL = 0,\n    FORMAT_JSON\n};\n\nstruct SHyprCtlCommand {\n    std::string                                                   name  = \"\";\n    bool                                                          exact = true;\n    std::function<std::string(eHyprCtlOutputFormat, std::string)> fn;\n};\n\nstruct SDispatchResult {\n    bool        passEvent = false;\n    bool        success   = true;\n    std::string error;\n};\n\nusing WINDOWID    = int64_t;\nusing MONITORID   = int64_t;\nusing WORKSPACEID = int64_t;\n"
  },
  {
    "path": "src/config/ConfigDataValues.hpp",
    "content": "#pragma once\n#include \"../defines.hpp\"\n#include \"../helpers/varlist/VarList.hpp\"\n#include <vector>\n#include <map>\n\nenum eConfigValueDataTypes : int8_t {\n    CVD_TYPE_INVALID     = -1,\n    CVD_TYPE_GRADIENT    = 0,\n    CVD_TYPE_CSS_VALUE   = 1,\n    CVD_TYPE_FONT_WEIGHT = 2,\n};\n\nclass ICustomConfigValueData {\n  public:\n    virtual ~ICustomConfigValueData() = default;\n\n    virtual eConfigValueDataTypes getDataType() = 0;\n\n    virtual std::string           toString() = 0;\n};\n\nclass CGradientValueData : public ICustomConfigValueData {\n  public:\n    CGradientValueData() = default;\n    CGradientValueData(CHyprColor col) {\n        m_colors.push_back(col);\n        updateColorsOk();\n    };\n    virtual ~CGradientValueData() = default;\n\n    virtual eConfigValueDataTypes getDataType() {\n        return CVD_TYPE_GRADIENT;\n    }\n\n    void reset(CHyprColor col) {\n        m_colors.clear();\n        m_colors.emplace_back(col);\n        m_angle = 0;\n        updateColorsOk();\n    }\n\n    void updateColorsOk() {\n        m_colorsOkLabA.clear();\n        for (auto& c : m_colors) {\n            const auto OKLAB = c.asOkLab();\n            m_colorsOkLabA.emplace_back(OKLAB.l);\n            m_colorsOkLabA.emplace_back(OKLAB.a);\n            m_colorsOkLabA.emplace_back(OKLAB.b);\n            m_colorsOkLabA.emplace_back(c.a);\n        }\n    }\n\n    /* Vector containing the colors */\n    std::vector<CHyprColor> m_colors;\n\n    /* Vector containing pure colors for shoving into opengl */\n    std::vector<float> m_colorsOkLabA;\n\n    /* Float corresponding to the angle (rad) */\n    float m_angle = 0;\n\n    //\n    bool operator==(const CGradientValueData& other) const {\n        if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle)\n            return false;\n\n        for (size_t i = 0; i < m_colors.size(); ++i)\n            if (m_colors[i] != other.m_colors[i])\n                return false;\n\n        return true;\n    }\n\n    virtual std::string toString() {\n        std::string result;\n        for (auto& c : m_colors) {\n            result += std::format(\"{:x} \", c.getAsHex());\n        }\n\n        result += std::format(\"{}deg\", sc<int>(m_angle * 180.0 / M_PI));\n        return result;\n    }\n};\n\nclass CCssGapData : public ICustomConfigValueData {\n  public:\n    CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {};\n    CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {};\n    CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {};\n    CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {};\n    CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {};\n\n    /* Css like directions */\n    int64_t m_top;\n    int64_t m_right;\n    int64_t m_bottom;\n    int64_t m_left;\n\n    void    parseGapData(CVarList2 varlist) {\n        const auto toInt = [](std::string_view string) -> int { return std::stoi(std::string(string)); };\n\n        switch (varlist.size()) {\n            case 1: {\n                *this = CCssGapData(toInt(varlist[0]));\n                break;\n            }\n            case 2: {\n                *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]));\n                break;\n            }\n            case 3: {\n                *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]));\n                break;\n            }\n            case 4: {\n                *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));\n                break;\n            }\n            default: {\n                Log::logger->log(Log::WARN, \"Too many arguments provided for gaps.\");\n                *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));\n                break;\n            }\n        }\n    }\n\n    void reset(int64_t global) {\n        m_top    = global;\n        m_right  = global;\n        m_bottom = global;\n        m_left   = global;\n    }\n\n    virtual eConfigValueDataTypes getDataType() {\n        return CVD_TYPE_CSS_VALUE;\n    }\n\n    virtual std::string toString() {\n        return std::format(\"{} {} {} {}\", m_top, m_right, m_bottom, m_left);\n    }\n};\n\nclass CFontWeightConfigValueData : public ICustomConfigValueData {\n  public:\n    CFontWeightConfigValueData() = default;\n    CFontWeightConfigValueData(const char* weight) {\n        parseWeight(weight);\n    }\n\n    int64_t                       m_value = 400; // default to normal weight\n\n    virtual eConfigValueDataTypes getDataType() {\n        return CVD_TYPE_FONT_WEIGHT;\n    }\n\n    virtual std::string toString() {\n        return std::format(\"{}\", m_value);\n    }\n\n    void parseWeight(const std::string& strWeight) {\n        auto lcWeight{strWeight};\n        std::ranges::transform(strWeight, lcWeight.begin(), ::tolower);\n\n        // values taken from Pango weight enums\n        const auto WEIGHTS = std::map<std::string, int>{\n            {\"thin\", 100},   {\"ultralight\", 200}, {\"light\", 300}, {\"semilight\", 350}, {\"book\", 380},  {\"normal\", 400},\n            {\"medium\", 500}, {\"semibold\", 600},   {\"bold\", 700},  {\"ultrabold\", 800}, {\"heavy\", 900}, {\"ultraheavy\", 1000},\n        };\n\n        auto weight = WEIGHTS.find(lcWeight);\n        if (weight != WEIGHTS.end())\n            m_value = weight->second;\n        else {\n            int w_i = std::stoi(strWeight);\n            if (w_i < 100 || w_i > 1000)\n                m_value = 400;\n        }\n    }\n};\n"
  },
  {
    "path": "src/config/ConfigDescriptions.hpp",
    "content": "#pragma once\n\n#include <climits>\n#include \"ConfigManager.hpp\"\n\ninline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {\n\n    /*\n     * general:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"general:border_size\",\n        .description = \"size of the border around windows\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:gaps_in\",\n        .description = \"gaps between windows\\n\\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"5\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:gaps_out\",\n        .description = \"gaps between windows and monitor edges\\n\\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"20\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:float_gaps\",\n        .description = \"gaps between windows and monitor edges for floating windows\\n\\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20). \\n-1 means default \"\n                       \"gaps_in/gaps_out\\n0 means no gaps\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"0\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:gaps_workspaces\",\n        .description = \"gaps between workspaces. Stacks with gaps_out.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 100},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:col.inactive_border\",\n        .description = \"border color for inactive windows\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0xff444444\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:col.active_border\",\n        .description = \"border color for the active window\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0xffffffff\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:col.nogroup_border\",\n        .description = \"inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0xffffaaff\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:col.nogroup_border_active\",\n        .description = \"active border color for window that cannot be added to a group\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0xffff00ff\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:layout\",\n        .description = \"which layout to use. [dwindle/master]\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"dwindle\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:no_focus_fallback\",\n        .description = \"if true, will not fall back to the next available window when moving focus in a direction where no window was found\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:resize_on_border\",\n        .description = \"enables resizing windows by clicking and dragging on borders and gaps\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:extend_border_grab_area\",\n        .description = \"extends the area around the border where you can click and drag on, only used when general:resize_on_border is on.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{15, 0, 100},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:hover_icon_on_border\",\n        .description = \"show a cursor icon when hovering over borders, only used when general:resize_on_border is on.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:allow_tearing\",\n        .description = \"master switch for allowing tearing to occur. See the Tearing page.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:resize_corner\",\n        .description = \"force floating windows to use a specific corner when being resized (1-4 going clockwise from top left, 0 to disable)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 4},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:snap:enabled\",\n        .description = \"enable snapping for floating windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:snap:window_gap\",\n        .description = \"minimum gap in pixels between windows before snapping\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{10, 0, 100},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:snap:monitor_gap\",\n        .description = \"minimum gap in pixels between window and monitor edges before snapping\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{10, 0, 100},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:snap:border_overlap\",\n        .description = \"if true, windows snap such that only one border's worth of space is between them\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:snap:respect_gaps\",\n        .description = \"if true, snapping will respect gaps between windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:modal_parent_blocking\",\n        .description = \"if true, parent windows of modals will not be interactive.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"general:locale\",\n        .description = \"overrides the system locale\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"},\n    },\n\n    /*\n     * decoration:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"decoration:rounding\",\n        .description = \"rounded corners' radius (in layout px)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:rounding_power\",\n        .description = \"rounding power of corners (2 is a circle)\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{2, 2, 10},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:active_opacity\",\n        .description = \"opacity of active windows. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:inactive_opacity\",\n        .description = \"opacity of inactive windows. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:fullscreen_opacity\",\n        .description = \"opacity of fullscreen windows. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:enabled\",\n        .description = \"enable drop shadows on windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:range\",\n        .description = \"Shadow range (size) in layout px\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{4, 0, 100},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:render_power\",\n        .description = \"in what power to render the falloff (more power, the faster the falloff) [1 - 4]\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{3, 1, 4},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:sharp\",\n        .description = \"whether the shadow should be sharp or not. Akin to an infinitely high render power.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:ignore_window\",\n        .description = \"if true, the shadow will not be rendered behind the window itself, only around it.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:color\",\n        .description = \"shadow's color. Alpha dictates shadow's opacity.\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0xee1a1a1a},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:color_inactive\",\n        .description = \"inactive shadow color. (if not set, will fall back to col.shadow)\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{}, //TODO: UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:offset\",\n        .description = \"shadow's rendering offset.\",\n        .type        = CONFIG_OPTION_VECTOR,\n        .data        = SConfigOptionDescription::SVectorData{{}, {-250, -250}, {250, 250}},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:shadow:scale\",\n        .description = \"shadow's scale. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:dim_modal\",\n        .description = \"enables dimming of parents of modal windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:dim_inactive\",\n        .description = \"enables dimming of inactive windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:dim_strength\",\n        .description = \"how much inactive windows should be dimmed [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.5, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:dim_special\",\n        .description = \"how much to dim the rest of the screen by when a special workspace is open. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.2, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:dim_around\",\n        .description = \"how much the dimaround window rule should dim by. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.4, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:screen_shader\",\n        .description = \"screen_shader a path to a custom shader to be applied at the end of rendering. See examples/screenShader.frag for an example.\",\n        .type        = CONFIG_OPTION_STRING_LONG,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:border_part_of_window\",\n        .description = \"whether the border should be treated as a part of the window.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n\n    /*\n     * blur:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:enabled\",\n        .description = \"enable kawase window background blur\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:size\",\n        .description = \"blur size (distance)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{8, 0, 100},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:passes\",\n        .description = \"the amount of passes to perform\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 10},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:ignore_opacity\",\n        .description = \"make the blur layer ignore the opacity of the window\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:new_optimizations\",\n        .description = \"whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:xray\",\n        .description = \"if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating \"\n                       \"blur significantly.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:noise\",\n        .description = \"how much noise to apply. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.0117, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:contrast\",\n        .description = \"contrast modulation for blur. [0.0 - 2.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.8916, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:brightness\",\n        .description = \"brightness modulation for blur. [0.0 - 2.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.8172, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:vibrancy\",\n        .description = \"Increase saturation of blurred colors. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.1696, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:vibrancy_darkness\",\n        .description = \"How strong the effect of vibrancy is on dark areas . [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:special\",\n        .description = \"whether to blur behind the special workspace (note: expensive)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:popups\",\n        .description = \"whether to blur popups (e.g. right-click menus)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:popups_ignorealpha\",\n        .description = \"works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.2, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:input_methods\",\n        .description = \"whether to blur input methods (e.g. fcitx5)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"decoration:blur:input_methods_ignorealpha\",\n        .description = \"works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.2, 0, 1},\n    },\n\n    /*\n     * animations:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"animations:enabled\",\n        .description = \"enable animations\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"animations:workspace_wraparound\",\n        .description = \"changes the direction of slide animations between the first and last workspaces\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n\n    /*\n     * input:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"input:kb_model\",\n        .description = \"Appropriate XKB keymap parameter. See the note below.\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{STRVAL_EMPTY},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:kb_layout\",\n        .description = \"Appropriate XKB keymap parameter\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"us\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:kb_variant\",\n        .description = \"Appropriate XKB keymap parameter\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:kb_options\",\n        .description = \"Appropriate XKB keymap parameter\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:kb_rules\",\n        .description = \"Appropriate XKB keymap parameter\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:kb_file\",\n        .description = \"Appropriate XKB keymap file\",\n        .type        = CONFIG_OPTION_STRING_LONG,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:numlock_by_default\",\n        .description = \"Engage numlock by default.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:resolve_binds_by_sym\",\n        .description = \"Determines how keybinds act when multiple layouts are used. If false, keybinds will always act as if the first specified layout is active. If true, \"\n                       \"keybinds specified by symbols are activated when you type the respective symbol with the current layout.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:repeat_rate\",\n        .description = \"The repeat rate for held-down keys, in repeats per second.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{25, 0, 200},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:repeat_delay\",\n        .description = \"Delay before a held-down key is repeated, in milliseconds.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{600, 0, 2000},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:sensitivity\",\n        .description = \"Sets the mouse input sensitivity. Value is clamped to the range -1.0 to 1.0.\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0, -1, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:accel_profile\",\n        .description = \"Sets the cursor acceleration profile. Can be one of adaptive, flat. Can also be custom, see below. Leave empty to use libinput's default mode for your \"\n                       \"input device. [adaptive/flat/custom]\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:force_no_accel\",\n        .description = \"Force no cursor acceleration. This bypasses most of your pointer settings to get as raw of a signal as possible. Enabling this is not recommended due to \"\n                       \"potential cursor desynchronization.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:rotation\",\n        .description = \"Sets the rotation of a device in degrees clockwise off the logical neutral position. Value is clamped to the range 0 to 359.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 359},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:left_handed\",\n        .description = \"Switches RMB and LMB\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:scroll_points\",\n        .description = \"Sets the scroll acceleration profile, when accel_profile is set to custom. Has to be in the form <step> <points>. Leave empty to have a flat scroll curve.\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:scroll_method\",\n        .description = \"Sets the scroll method. Can be one of 2fg (2 fingers), edge, on_button_down, no_scroll. [2fg/edge/on_button_down/no_scroll]\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:scroll_button\",\n        .description = \"Sets the scroll button. Has to be an int, cannot be a string. Check wev if you have any doubts regarding the ID. 0 means default.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 300},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:scroll_button_lock\",\n        .description = \"If the scroll button lock is enabled, the button does not need to be held down. Pressing and releasing the button toggles the button lock, which logically \"\n                       \"holds the button down or releases it. While the button is logically held down, motion events are converted to scroll events.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:scroll_factor\",\n        .description = \"Multiplier added to scroll movement for external mice. Note that there is a separate setting for touchpad scroll_factor.\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:natural_scroll\",\n        .description = \"Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:follow_mouse\",\n        .description = \"Specify if and how cursor movement should affect window focus. See the note below. [0/1/2/3]\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 3},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:follow_mouse_threshold\",\n        .description = \"The smallest distance in logical pixels the mouse needs to travel for the window under it to get focused. Works only with follow_mouse = 1.\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:focus_on_close\",\n        .description = \"Controls the window focus behavior when a window is closed. When set to 0, focus will shift to the next window candidate. When set to 1, focus will shift \"\n                       \"to the window under the cursor.\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"next,cursor\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:mouse_refocus\",\n        .description = \"if disabled, mouse focus won't switch to the hovered window unless the mouse crosses a window boundary when follow_mouse=1.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:float_switch_override_focus\",\n        .description = \"If enabled (1 or 2), focus will change to the window under the cursor when changing from tiled-to-floating and vice versa. If 2, focus will also follow \"\n                       \"mouse on float-to-float switches.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:special_fallthrough\",\n        .description = \"if enabled, having only floating windows in the special workspace will not block focusing windows in the regular workspace.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:off_window_axis_events\",\n        .description = \"Handles axis events around (gaps/border for tiled, dragarea/border for floated) a focused window. 0 ignores axis events 1 sends out-of-bound coordinates 2 \"\n                       \"fakes pointer coordinates to the closest point inside the window 3 warps the cursor to the closest point inside the window\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 3},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:emulate_discrete_scroll\",\n        .description = \"Emulates discrete scrolling from high resolution scrolling events. 0 disables it, 1 enables handling of non-standard events only, and 2 force enables all \"\n                       \"scroll wheel events to be handled\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 2},\n    },\n\n    /*\n     * input:touchpad:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:disable_while_typing\",\n        .description = \"Disable the touchpad while typing.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:natural_scroll\",\n        .description = \"Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:scroll_factor\",\n        .description = \"Multiplier applied to the amount of scroll movement.\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value = \"input:touchpad:middle_button_emulation\",\n        .description =\n            \"Sending LMB and RMB simultaneously will be interpreted as a middle click. This disables any touchpad area that would normally send a middle click based on location.\",\n        .type = CONFIG_OPTION_BOOL,\n        .data = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:tap_button_map\",\n        .description = \"Sets the tap button mapping for touchpad button emulation. Can be one of lrm (default) or lmr (Left, Middle, Right Buttons). [lrm/lmr]\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value = \"input:touchpad:clickfinger_behavior\",\n        .description =\n            \"Button presses with 1, 2, or 3 fingers will be mapped to LMB, RMB, and MMB respectively. This disables interpretation of clicks based on location on the touchpad.\",\n        .type = CONFIG_OPTION_BOOL,\n        .data = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:tap-to-click\",\n        .description = \"Tapping on the touchpad with 1, 2, or 3 fingers will send LMB, RMB, and MMB respectively.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:drag_lock\",\n        .description = \"When enabled, lifting the finger off while dragging will not drop the dragged item. 0 -> disabled, 1 -> enabled with timeout, 2 -> enabled sticky.\"\n                       \"dragging will not drop the dragged item.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:tap-and-drag\",\n        .description = \"Sets the tap and drag mode for the touchpad\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:flip_x\",\n        .description = \"Inverts the horizontal movement of the touchpad\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:flip_y\",\n        .description = \"Inverts the vertical movement of the touchpad\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchpad:drag_3fg\",\n        .description = \"Three Finger Drag 0 -> disabled, 1 -> 3 finger, 2 -> 4 finger\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 2},\n    },\n\n    /*\n     * input:touchdevice:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"input:touchdevice:transform\",\n        .description = \"Transform the input from touchdevices. The possible transformations are the same as those of the monitors\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchdevice:output\",\n        .description = \"The monitor to bind touch devices. The default is auto-detection. To stop auto-detection, use an empty string or the [[Empty]] value.\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:touchdevice:enabled\",\n        .description = \"Whether input is enabled for touch devices.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n\n    /*\n     * input:virtualkeyboard:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"input:virtualkeyboard:share_states\",\n        .description = \"Unify key down states and modifier states with other keyboards. 0 -> no, 1 -> yes, 2 -> yes unless IME client\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:virtualkeyboard:release_pressed_on_close\",\n        .description = \"Release all pressed keys by virtual keyboard on close.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * input:tablet:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"input:tablet:transform\",\n        .description = \"transform the input from tablets. The possible transformations are the same as those of the monitors\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:output\",\n        .description = \"the monitor to bind tablets. Can be current or a monitor name. Leave empty to map across all monitors.\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:region_position\",\n        .description = \"position of the mapped region in monitor layout relative to the top left corner of the bound monitor or all monitors.\",\n        .type        = CONFIG_OPTION_VECTOR,\n        .data        = SConfigOptionDescription::SVectorData{{}, {-20000, -20000}, {20000, 20000}},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:absolute_region_position\",\n        .description = \"whether to treat the region_position as an absolute position in monitor layout. Only applies when output is empty.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:region_size\",\n        .description = \"size of the mapped region. When this variable is set, tablet input will be mapped to the region. [0, 0] or invalid size means unset.\",\n        .type        = CONFIG_OPTION_VECTOR,\n        .data        = SConfigOptionDescription::SVectorData{{}, {-100, -100}, {4000, 4000}},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:relative_input\",\n        .description = \"whether the input should be relative\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:left_handed\",\n        .description = \"if enabled, the tablet will be rotated 180 degrees\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:active_area_size\",\n        .description = \"size of tablet's active area in mm\",\n        .type        = CONFIG_OPTION_VECTOR,\n        .data        = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}},\n    },\n    SConfigOptionDescription{\n        .value       = \"input:tablet:active_area_position\",\n        .description = \"position of the active area in mm\",\n        .type        = CONFIG_OPTION_VECTOR,\n        .data        = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}},\n    },\n\n    /* ##TODO\n     *\n     * PER DEVICE SETTINGS?\n     *\n     * */\n\n    /*\n     * gestures:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_distance\",\n        .description = \"in px, the distance of the touchpad gesture\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{300, 0, 2000}, //##TODO RANGE?\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_touch\",\n        .description = \"enable workspace swiping from the edge of a touchscreen\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_invert\",\n        .description = \"invert the direction (touchpad only)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_touch_invert\",\n        .description = \"invert the direction (touchscreen only)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_min_speed_to_force\",\n        .description = \"minimum speed in px per timepoint to force the change ignoring cancel_ratio. Setting to 0 will disable this mechanic.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{30, 0, 200}, //##TODO RANGE?\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_cancel_ratio\",\n        .description = \"how much the swipe has to proceed in order to commence it. (0.7 -> if > 0.7 * distance, switch, if less, revert) [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.5, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_create_new\",\n        .description = \"whether a swipe right on the last workspace should create a new one.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_direction_lock\",\n        .description = \"if enabled, switching direction will be locked when you swipe past the direction_lock_threshold (touchpad only).\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_direction_lock_threshold\",\n        .description = \"in px, the distance to swipe before direction lock activates (touchpad only).\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{10, 0, 200}, //##TODO RANGE?\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_forever\",\n        .description = \"if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:workspace_swipe_use_r\",\n        .description = \"if enabled, swiping will use the r prefix instead of the m prefix for finding workspaces.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"gestures:close_max_timeout\",\n        .description = \"Timeout for closing windows with the close gesture, in ms.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1000, 10, 2000},\n    },\n\n    /*\n     * group:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"group:insert_after_current\",\n        .description = \"whether new windows in a group spawn after current or at group tail\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:focus_removed_window\",\n        .description = \"whether Hyprland should focus on the window that has just been moved out of the group\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:merge_groups_on_drag\",\n        .description = \"whether window groups can be dragged into other groups\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:merge_groups_on_groupbar\",\n        .description = \"whether one group will be merged with another when dragged into its groupbar\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:col.border_active\",\n        .description = \"border color for inactive windows\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0x66ffff00\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:col.border_inactive\",\n        .description = \"border color for the active window\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0x66777700\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:col.border_locked_inactive\",\n        .description = \"inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0x66ff5500\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:col.border_locked_active\",\n        .description = \"active border color for window that cannot be added to a group\",\n        .type        = CONFIG_OPTION_GRADIENT,\n        .data        = SConfigOptionDescription::SGradientData{\"0x66775500\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:auto_group\",\n        .description = \"automatically group new windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:drag_into_group\",\n        .description = \"whether dragging a window into a unlocked group will merge them. Options: 0 (disabled), 1 (enabled), 2 (only when dragging into the groupbar)\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"disabled,enabled,only when dragging into the groupbar\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:merge_floated_into_tiled_on_groupbar\",\n        .description = \"whether dragging a floating window into a tiled window groupbar will merge them\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:group_on_movetoworkspace\",\n        .description = \"whether using movetoworkspace[silent] will merge the window into the workspace's solitary unlocked group\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * group:groupbar:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:enabled\",\n        .description = \"enables groupbars\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:font_family\",\n        .description = \"font used to display groupbar titles, use misc:font_family if not specified\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:font_weight_active\",\n        .description = \"weight of the font used to display active groupbar titles\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"normal\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:font_weight_inactive\",\n        .description = \"weight of the font used to display inactive groupbar titles\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"normal\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:font_size\",\n        .description = \"font size of groupbar title\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{8, 2, 64},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:gradients\",\n        .description = \"enables gradients\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:height\",\n        .description = \"height of the groupbar\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{14, 1, 64},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:indicator_gap\",\n        .description = \"height of the gap between the groupbar indicator and title\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 64},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:indicator_height\",\n        .description = \"height of the groupbar indicator\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{3, 1, 64},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:stacked\",\n        .description = \"render the groupbar as a vertical stack\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:priority\",\n        .description = \"sets the decoration priority for groupbars\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{3, 0, 6}, //##TODO RANGE?\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:render_titles\",\n        .description = \"whether to render titles in the group bar decoration\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:scrolling\",\n        .description = \"whether scrolling in the groupbar changes group active window\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:rounding\",\n        .description = \"how much to round the groupbar\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:rounding_power\",\n        .description = \"rounding power of groupbar corners (2 is a circle)\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{2, 2, 10},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:gradient_rounding\",\n        .description = \"how much to round the groupbar gradient\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:gradient_rounding_power\",\n        .description = \"rounding power of groupbar gradient corners (2 is a circle)\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{2, 2, 10},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:round_only_edges\",\n        .description = \"if yes, will only round at the groupbar edges\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:gradient_round_only_edges\",\n        .description = \"if yes, will only round at the groupbar gradient edges\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:text_color\",\n        .description = \"color for window titles in the groupbar\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0xffffffff},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:text_color_inactive\",\n        .description = \"color for inactive windows' titles in the groupbar (if unset, defaults to text_color)\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{}, //TODO: UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:text_color_locked_active\",\n        .description = \"color for the active window's title in a locked group (if unset, defaults to text_color)\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{}, //TODO: UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:text_color_locked_inactive\",\n        .description = \"color for inactive windows' titles in locked groups (if unset, defaults to text_color_inactive)\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{}, //TODO: UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:col.active\",\n        .description = \"active group border color\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0x66ffff00},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:col.inactive\",\n        .description = \"inactive (out of focus) group border color\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0x66777700},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:col.locked_active\",\n        .description = \"active locked group border color\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0x66ff5500},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:col.locked_inactive\",\n        .description = \"controls the group bar text color\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0x66775500},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:gaps_out\",\n        .description = \"gap between gradients and window\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:gaps_in\",\n        .description = \"gap between gradients\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:keep_upper_gap\",\n        .description = \"keep an upper gap above gradient\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:text_offset\",\n        .description = \"set an offset for a text\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SRangeData{0, -20, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:text_padding\",\n        .description = \"set horizontal padding for a text\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 22},\n    },\n    SConfigOptionDescription{\n        .value       = \"group:groupbar:blur\",\n        .description = \"enable background blur for groupbars\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * misc:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"misc:disable_hyprland_logo\",\n        .description = \"disables the random Hyprland logo / anime girl background. :(\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:disable_splash_rendering\",\n        .description = \"disables the Hyprland splash rendering. (requires a monitor reload to take effect)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:col.splash\",\n        .description = \"Changes the color of the splash text (requires a monitor reload to take effect).\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0xffffffff},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:font_family\",\n        .description = \"Set the global default font to render the text including debug fps/notification, config error messages and etc., selected from system fonts.\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"Sans\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:splash_font_family\",\n        .description = \"Changes the font used to render the splash text, selected from system fonts (requires a monitor reload to take effect).\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:force_default_wallpaper\",\n        .description = \"Enforce any of the 3 default wallpapers. Setting this to 0 or 1 disables the anime background. -1 means “random”. [-1/0/1/2]\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{-1, -1, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:vfr\",\n        .description = \"controls the VFR status of Hyprland. Heavily recommended to leave enabled to conserve resources.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:vrr\",\n        .description = \"\tcontrols the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only, 3 - fullscreen with game or video content type [0/1/2/3]\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 3},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:mouse_move_enables_dpms\",\n        .description = \"If DPMS is set to off, wake up the monitors if the mouse move\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:key_press_enables_dpms\",\n        .description = \"If DPMS is set to off, wake up the monitors if a key is pressed.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:name_vk_after_proc\",\n        .description = \"Name virtual keyboards after the processes that create them. E.g. /usr/bin/fcitx5 will have hl-virtual-keyboard-fcitx5.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:always_follow_on_dnd\",\n        .description = \"Will make mouse focus follow the mouse when drag and dropping. Recommended to leave it enabled, especially for people using focus follows mouse at 0.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:layers_hog_keyboard_focus\",\n        .description = \"If true, will make keyboard-interactive layers keep their focus on mouse move (e.g. wofi, bemenu)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:animate_manual_resizes\",\n        .description = \"If true, will animate manual window resizes/moves\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:animate_mouse_windowdragging\",\n        .description = \"If true, will animate windows being dragged by mouse, note that this can cause weird behavior on some curves\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:disable_autoreload\",\n        .description = \"If true, the config will not reload automatically on save, and instead needs to be reloaded with hyprctl reload. Might save on battery.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:enable_swallow\",\n        .description = \"Enable window swallowing\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value = \"misc:swallow_regex\",\n        .description =\n            \"The class regex to be used for windows that should be swallowed (usually, a terminal). To know more about the list of regex which can be used use this cheatsheet.\",\n        .type = CONFIG_OPTION_STRING_SHORT,\n        .data = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:swallow_exception_regex\",\n        .description = \"The title regex to be used for windows that should not be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the \"\n                       \"parent (e.g. Kitty) window’s title on the assumption that it changes to whatever process it’s running.\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:focus_on_activate\",\n        .description = \"Whether Hyprland should focus an app that requests to be focused (an activate request)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:mouse_move_focuses_monitor\",\n        .description = \"Whether mouse moving into a different monitor should focus it\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:allow_session_lock_restore\",\n        .description = \"if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:session_lock_xray\",\n        .description = \"keep rendering workspaces below your lockscreen\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:background_color\",\n        .description = \"change the background color. (requires enabled disable_hyprland_logo)\",\n        .type        = CONFIG_OPTION_COLOR,\n        .data        = SConfigOptionDescription::SColorData{0x111111},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:close_special_on_empty\",\n        .description = \"close the special workspace if the last window is removed\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:on_focus_under_fullscreen\",\n        .description = \"if there is a fullscreen or maximized window, decide whether a tiled window requested to focus should replace it, stay behind or disable the \"\n                       \"fullscreen/maximized state. 0 - ignore focus request (keep focus on fullscreen window), 1 - takes over, 2 - unfullscreen/unmaximize [0/1/2]\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:exit_window_retains_fullscreen\",\n        .description = \"if true, closing a fullscreen window makes the next focused window fullscreen\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:initial_workspace_tracking\",\n        .description = \"if enabled, windows will open on the workspace they were invoked on. 0 - disabled, 1 - single-shot, 2 - persistent (all children too)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:middle_click_paste\",\n        .description = \"whether to enable middle-click-paste (aka primary selection)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:render_unfocused_fps\",\n        .description = \"the maximum limit for renderunfocused windows' fps in the background\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{15, 1, 120},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:disable_xdg_env_checks\",\n        .description = \"disable the warning if XDG environment is externally managed\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:disable_hyprland_guiutils_check\",\n        .description = \"disable the warning if hyprland-guiutils is missing\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:disable_watchdog_warning\",\n        .description = \"whether to disable the warning about not using start-hyprland.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:lockdead_screen_delay\",\n        .description = \"the delay in ms after the lockdead screen appears if the lock screen did not appear after a lock event occurred.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1000, 0, 5000},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:enable_anr_dialog\",\n        .description = \"whether to enable the ANR (app not responding) dialog when your apps hang\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:anr_missed_pings\",\n        .description = \"number of missed pings before showing the ANR dialog\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{5, 1, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:screencopy_force_8b\",\n        .description = \"forces 8 bit screencopy (fixes apps that don't understand 10bit)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:disable_scale_notification\",\n        .description = \"disables notification popup when a monitor fails to set a suitable scale and falls back to suggested\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"misc:size_limits_tiled\",\n        .description = \"whether to apply minsize and maxsize rules to tiled windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * binds:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"binds:pass_mouse_when_bound\",\n        .description = \"if disabled, will not pass the mouse events to apps / dragging windows around if a keybind has been triggered.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:scroll_event_delay\",\n        .description = \"in ms, how many ms to wait after a scroll event to allow passing another one for the binds.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{300, 0, 2000},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:workspace_back_and_forth\",\n        .description = \"If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3’s auto_back_and_forth.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:hide_special_on_workspace_change\",\n        .description = \"If enabled, changing the active workspace (including to itself) will hide the special workspace on the monitor where the newly active workspace resides.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:allow_workspace_cycles\",\n        .description = \"If enabled, workspaces don’t forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly \"\n                       \"going to the previous workspace.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:workspace_center_on\",\n        .description = \"Whether switching workspaces should center the cursor on the workspace (0) or on the last active window for that workspace (1)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:focus_preferred_method\",\n        .description = \"sets the preferred focus finding method when using focuswindow/movewindow/etc with a direction. 0 - history (recent have priority), 1 - length (longer \"\n                       \"shared edges have priority)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:ignore_group_lock\",\n        .description = \"If enabled, dispatchers like moveintogroup, moveoutofgroup and movewindoworgroup will ignore lock per group.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:movefocus_cycles_fullscreen\",\n        .description = \"If enabled, when on a fullscreen window, movefocus will cycle fullscreen, if not, it will move the focus in a direction.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:movefocus_cycles_groupfirst\",\n        .description = \"If enabled, when in a grouped window, movefocus will cycle windows in the groups first, then at each ends of tabs, it'll move on to other windows/groups\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:disable_keybind_grabbing\",\n        .description = \"If enabled, apps that request keybinds to be disabled (e.g. VMs) will not be able to do so.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:window_direction_monitor_fallback\",\n        .description = \"If enabled, moving a window or focus over the edge of a monitor with a direction will move it to the next monitor in that direction.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:allow_pin_fullscreen\",\n        .description = \"Allows fullscreen to pinned windows, and restore their pinned status afterwards\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"binds:drag_threshold\",\n        .description = \"Movement threshold in pixels for window dragging and c/g bind flags. 0 to disable and grab on mousedown.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, INT_MAX},\n    },\n\n    /*\n     * xwayland:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"xwayland:enabled\",\n        .description = \"allow running applications using X11\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"xwayland:use_nearest_neighbor\",\n        .description = \"uses the nearest neighbor filtering for xwayland apps, making them pixelated rather than blurry\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"xwayland:force_zero_scaling\",\n        .description = \"forces a scale of 1 on xwayland windows on scaled displays.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"xwayland:create_abstract_socket\",\n        .description = \"Create the abstract Unix domain socket for XWayland\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * opengl:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"opengl:nvidia_anti_flicker\",\n        .description = \"reduces flickering on nvidia at the cost of possible frame drops on lower-end GPUs. On non-nvidia, this is ignored.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n\n    /*\n     * render:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"render:direct_scanout\",\n        .description = \"Enables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also \"\n                       \"recommended to set this to false if the fullscreen application shows graphical glitches. 0 - off, 1 - on, 2 - auto (on with content type 'game')\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:expand_undersized_textures\",\n        .description = \"Whether to expand textures that have not yet resized to be larger, or to just stretch them instead.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:xp_mode\",\n        .description = \"Disable back buffer and bottom layer rendering.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:ctm_animation\",\n        .description = \"Whether to enable a fade animation for CTM changes (hyprsunset). 2 means 'auto' (Yes on everything but Nvidia).\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:cm_fs_passthrough\",\n        .description = \"Passthrough color settings for fullscreen apps when possible\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:cm_enabled\",\n        .description = \"Enable Color Management pipelines (requires restart to fully take effect)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:send_content_type\",\n        .description = \"Report content type to allow monitor profile autoswitch (may result in a black screen during the switch)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:cm_auto_hdr\",\n        .description = \"Auto-switch to hdr mode when fullscreen app is in hdr, 0 - off, 1 - hdr, 2 - hdredid (cm_fs_passthrough can switch to hdr even when this setting is off)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 1, .min = 0, .max = 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:new_render_scheduling\",\n        .description = \"enable new render scheduling, which should improve FPS on underpowered devices. This does not add latency when your PC can keep up.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:non_shader_cm\",\n        .description = \"Enable CM without shader. 0 - disable, 1 - whenever possible, 2 - DS and passthrough only, 3 - disable and ignore CM issues\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"disable,always,ondemand,ignore\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:cm_sdr_eotf\",\n        .description = \"Default transfer function for displaying SDR apps. default - Use default value (Gamma 2.2), gamma22 - Treat unspecified as Gamma 2.2, gamma22force - Treat \"\n                       \"unspecified and sRGB as Gamma 2.2, srgb - Treat unspecified as sRGB\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"default\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:commit_timing_enabled\",\n        .description = \"Enable commit timing proto. Requires restart\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"render:icc_vcgt_enabled\",\n        .description = \"Enable sending VCGT ramps to KMS with ICC profiles\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    {\n        .value       = \"render:use_shader_blur_blend\",\n        .description = \"Use experimental blurred bg blending\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * cursor:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"cursor:invisible\",\n        .description = \"don't render cursors\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:no_hardware_cursors\",\n        .description = \"disables hardware cursors. Auto = disable when multi-gpu on nvidia\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"Disabled,Enabled,Auto\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:no_break_fs_vrr\",\n        .description = \"disables scheduling new frames on cursor movement for fullscreen apps with VRR enabled to avoid framerate spikes (may require no_hardware_cursors = true) \"\n                       \"0 - off, 1 - on, 2 - auto (on with content type 'game')\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:min_refresh_rate\",\n        .description = \"minimum refresh rate for cursor movement when no_break_fs_vrr is active. Set to minimum supported refresh rate or higher\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{24, 10, 500},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:hotspot_padding\",\n        .description = \"the padding, in logical px, between screen edges and the cursor\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{1, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:inactive_timeout\",\n        .description = \"in seconds, after how many seconds of cursor’s inactivity to hide it. Set to 0 for never.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:no_warps\",\n        .description = \"if true, will not warp the cursor in many cases (focusing, keybinds, etc)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:persistent_warps\",\n        .description = \"When a window is refocused, the cursor returns to its last position relative to that window, rather than to the centre.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:warp_on_change_workspace\",\n        .description = \"Move the cursor to the last focused window after changing the workspace. Options: 0 (Disabled), 1 (Enabled), 2 (Force - ignores cursor:no_warps option)\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"Disabled,Enabled,Force\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:warp_on_toggle_special\",\n        .description = \"Move the cursor to the last focused window when toggling a special workspace. Options: 0 (Disabled), 1 (Enabled), \"\n                       \"2 (Force - ignores cursor:no_warps option)\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"Disabled,Enabled,Force\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:default_monitor\",\n        .description = \"the name of a default monitor for the cursor to be set to on startup (see hyprctl monitors for names)\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"\"}, //##TODO UNSET?\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:zoom_factor\",\n        .description = \"the factor to zoom by around the cursor. Like a magnifying glass. Minimum 1.0 (meaning no zoom)\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 1, 10},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:zoom_rigid\",\n        .description = \"whether the zoom should follow the cursor rigidly (cursor is always centered if it can be) or loosely\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:zoom_disable_aa\",\n        .description = \"If enabled, when zooming, no antialiasing will be used (zoom will be pixelated)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:zoom_detached_camera\",\n        .description = \"Detaches the camera from the mouse when zoomed in, only ever moving to keep the mouse in view\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:enable_hyprcursor\",\n        .description = \"whether to enable hyprcursor support\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:hide_on_key_press\",\n        .description = \"Hides the cursor when you press any key until the mouse is moved.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:hide_on_touch\",\n        .description = \"Hides the cursor when the last input was a touch input until a mouse input is done.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:hide_on_tablet\",\n        .description = \"Hides the cursor when the last input was a tablet input until a mouse input is done.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:use_cpu_buffer\",\n        .description = \"Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. 0 - off, 1 - on, 2 - auto (nvidia only)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:sync_gsettings_theme\",\n        .description = \"sync xcursor theme with gsettings, it applies cursor-theme and cursor-size on theme load to gsettings making most CSD gtk based clients use same xcursor \"\n                       \"theme and size.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"cursor:warp_back_after_non_mouse_input\",\n        .description = \"warp the cursor back to where it was after using a non-mouse input to move it, and then returning back to mouse.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * ecosystem:\n     */\n    SConfigOptionDescription{\n        .value       = \"ecosystem:no_update_news\",\n        .description = \"disable the popup that shows up when you update hyprland to a new version.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"ecosystem:no_donation_nag\",\n        .description = \"disable the popup that shows up twice a year encouraging to donate.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"ecosystem:enforce_permissions\",\n        .description = \"whether to enable permission control (see https://wiki.hypr.land/Configuring/Permissions/).\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * debug:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"debug:overlay\",\n        .description = \"print the debug performance overlay. Disable VFR for accurate results.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:damage_blink\",\n        .description = \"disable logging to a file\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:gl_debugging\",\n        .description = \"enable OpenGL debugging and error checking, they hurt performance.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:disable_logs\",\n        .description = \"disable logging to a file\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:disable_time\",\n        .description = \"disables time logging\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:damage_tracking\",\n        .description = \"redraw only the needed bits of the display. Do not change. (default: full - 2) monitor - 1, none - 0\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:enable_stdout_logs\",\n        .description = \"enables logging to stdout\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:manual_crash\",\n        .description = \"set to 1 and then back to 0 to crash Hyprland.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:suppress_errors\",\n        .description = \"if true, do not display config file parsing errors.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:disable_scale_checks\",\n        .description = \"disables verification of the scale factors. Will result in pixel alignment and rounding errors.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:error_limit\",\n        .description = \"limits the number of displayed config file parsing errors.\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{5, 0, 20},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:error_position\",\n        .description = \"sets the position of the error bar. top - 0, bottom - 1\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{0, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:colored_stdout_logs\",\n        .description = \"enables colors in the stdout logs.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:log_damage\",\n        .description = \"enables logging the damage.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:pass\",\n        .description = \"enables render pass debugging.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:full_cm_proto\",\n        .description = \"claims support for all cm proto features (requires restart)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:ds_handle_same_buffer\",\n        .description = \"Special case for DS with unmodified buffer\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:ds_handle_same_buffer_fifo\",\n        .description = \"Special case for DS with unmodified buffer unlocks fifo\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:fifo_pending_workaround\",\n        .description = \"Fifo workaround for empty pending list\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"debug:render_solitary_wo_damage\",\n        .description = \"Render solitary window with empty damage\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * layout:\n     */\n    SConfigOptionDescription{\n        .value       = \"layout:single_window_aspect_ratio\",\n        .description = \"If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio.  Ignored if the y-value is zero.\",\n        .type        = CONFIG_OPTION_VECTOR,\n        .data        = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},\n    },\n    SConfigOptionDescription{\n        .value       = \"layout:single_window_aspect_ratio_tolerance\",\n        .description = \"Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},\n    },\n\n    /*\n     * dwindle:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"dwindle:pseudotile\",\n        .description = \"enable pseudotiling. Pseudotiled windows retain their floating size when tiled.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:force_split\",\n        .description = \"0 -> split follows mouse, 1 -> always split to the left (new = left or top) 2 -> always split to the right (new = right or bottom)\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"follow mouse,left or top,right or bottom\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:preserve_split\",\n        .description = \"if enabled, the split (side/top) will not change regardless of what happens to the container.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:smart_split\",\n        .description = \"if enabled, allows a more precise control over the window split direction based on the cursor's position. The window is conceptually divided into four \"\n                       \"triangles, and cursor's triangle determines the split direction. This feature also turns on preserve_split.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value = \"dwindle:smart_resizing\",\n        .description =\n            \"if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position.\",\n        .type = CONFIG_OPTION_BOOL,\n        .data = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:permanent_direction_override\",\n        .description = \"if enabled, makes the preselect direction persist until either this mode is turned off, another direction is specified, or a non-direction is specified \"\n                       \"(anything other than l,r,u/t,d/b)\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:special_scale_factor\",\n        .description = \"specifies the scale factor of windows on the special workspace [0 - 1]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:split_width_multiplier\",\n        .description = \"specifies the auto-split width multiplier\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0.1, 3},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:use_active_for_splits\",\n        .description = \"whether to prefer the active window or the mouse position for splits\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:default_split_ratio\",\n        .description = \"the default split ratio on window open. 1 means even 50/50 split. [0.1 - 1.9]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0.1, 1.9},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:split_bias\",\n        .description = \"specifies which window will receive the split ratio. 0 -> directional (the top or left window), 1 -> the current window\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{0, \"directional,current\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"dwindle:precise_mouse_move\",\n        .description = \"if enabled, bindm movewindow will drop the window more precisely depending on where your mouse is. This feature also turns on preserve_split.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * master:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"master:allow_small_split\",\n        .description = \"enable adding additional master windows in a horizontal split style\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:special_scale_factor\",\n        .description = \"the scale of the special workspace windows. [0.0 - 1.0]\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{1, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value = \"master:mfact\",\n        .description =\n            \"the size as a percentage of the master window, for example `mfact = 0.70` would mean 70% of the screen will be the master window, and 30% the slave [0.0 - 1.0]\",\n        .type = CONFIG_OPTION_FLOAT,\n        .data = SConfigOptionDescription::SFloatData{0.55, 0, 1},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:new_status\",\n        .description = \"`master`: new window becomes master; `slave`: new windows are added to slave stack; `inherit`: inherit from focused window\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"slave\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:new_on_top\",\n        .description = \"whether a newly open window should be on the top of the stack\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:new_on_active\",\n        .description = \"`before`, `after`: place new window relative to the focused window; `none`: place new window according to the value of `new_on_top`. \",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"none\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:orientation\",\n        .description = \"default placement of the master area, can be left, right, top, bottom or center\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"left\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:slave_count_for_center_master\",\n        .description = \"when using orientation=center, make the master window centered only when at least this many slave windows are open. (Set 0 to always_center_master)\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{2, 0, 10}, //##TODO RANGE?\n    },\n    SConfigOptionDescription{.value       = \"master:center_master_fallback\",\n                             .description = \"Set fallback for center master when slaves are less than slave_count_for_center_master, can be left ,right ,top ,bottom\",\n                             .type        = CONFIG_OPTION_STRING_SHORT,\n                             .data        = SConfigOptionDescription::SStringData{\"left\"}},\n    SConfigOptionDescription{\n        .value       = \"master:center_ignores_reserved\",\n        .description = \"centers the master window on monitor ignoring reserved areas\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n    SConfigOptionDescription{\n        .value = \"master:smart_resizing\",\n        .description =\n            \"if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position.\",\n        .type = CONFIG_OPTION_BOOL,\n        .data = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:drop_at_cursor\",\n        .description = \"when enabled, dragging and dropping windows will put them at the cursor position. Otherwise, when dropped at the stack side, they will go to the \"\n                       \"top/bottom of the stack depending on new_on_top.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"master:always_keep_position\",\n        .description = \"whether to keep the master window in its configured position when there are no slave windows\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{false},\n    },\n\n    /*\n     * scrolling:\n     */\n\n    SConfigOptionDescription{\n        .value       = \"scrolling:fullscreen_on_one_column\",\n        .description = \"when enabled, a single column on a workspace will always span the entire screen.\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:column_width\",\n        .description = \"the default width of a column, [0.1 - 1.0].\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{.value = 0.5, .min = 0.1, .max = 1.0},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:focus_fit_method\",\n        .description = \"When a column is focused, what method should be used to bring it into view\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = \"center,fit\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:follow_focus\",\n        .description = \"when a window is focused, should the layout move to bring it into view automatically\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{.value = true},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:follow_min_visible\",\n        .description = \"when a window is focused, require that at least a given fraction of it is visible for focus to follow\",\n        .type        = CONFIG_OPTION_FLOAT,\n        .data        = SConfigOptionDescription::SFloatData{.value = 0.4, .min = 0.0, .max = 1.0},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:explicit_column_widths\",\n        .description = \"A comma-separated list of preconfigured widths for colresize +conf/-conf\",\n        .type        = CONFIG_OPTION_STRING_SHORT,\n        .data        = SConfigOptionDescription::SStringData{\"0.333, 0.5, 0.667, 1.0\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:direction\",\n        .description = \"Direction in which new windows appear and the layout scrolls\",\n        .type        = CONFIG_OPTION_CHOICE,\n        .data        = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = \"right,left,down,up\"},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:wrap_focus\",\n        .description = \"Determines if column focus wraps around when going before the first column or past the last column\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{.value = true},\n    },\n    SConfigOptionDescription{\n        .value       = \"scrolling:wrap_swapcol\",\n        .description = \"Determines if column movement wraps around when moving to before the first column or past the last column\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{.value = true},\n    },\n\n    /*\n     * Quirks\n    */\n\n    SConfigOptionDescription{\n        .value       = \"quirks:prefer_hdr\",\n        .description = \"Prefer HDR mode. 0 - off, 1 - always, 2 - gamescope only\",\n        .type        = CONFIG_OPTION_INT,\n        .data        = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2},\n    },\n    SConfigOptionDescription{\n        .value       = \"quirks:skip_non_kms_dmabuf_formats\",\n        .description = \"Do not report dmabuf formats which cannot be imported into KMS\",\n        .type        = CONFIG_OPTION_BOOL,\n        .data        = SConfigOptionDescription::SBoolData{true},\n    },\n\n};\n"
  },
  {
    "path": "src/config/ConfigManager.cpp",
    "content": "#include <re2/re2.h>\n\n#include \"ConfigManager.hpp\"\n#include \"ConfigWatcher.hpp\"\n#include \"../managers/KeybindManager.hpp\"\n#include \"../Compositor.hpp\"\n\n#include \"../render/decorations/CHyprGroupBarDecoration.hpp\"\n#include \"config/ConfigDataValues.hpp\"\n#include \"config/ConfigValue.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../xwayland/XWayland.hpp\"\n#include \"../protocols/OutputManagement.hpp\"\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"../desktop/rule/Engine.hpp\"\n#include \"../desktop/rule/windowRule/WindowRule.hpp\"\n#include \"../desktop/rule/layerRule/LayerRule.hpp\"\n#include \"../debug/HyprCtl.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../layout/space/Space.hpp\"\n#include \"../layout/supplementary/WorkspaceAlgoMatcher.hpp\"\n#include \"defaultConfig.hpp\"\n\n#include \"../render/Renderer.hpp\"\n#include \"../hyprerror/HyprError.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../managers/EventManager.hpp\"\n#include \"../managers/permissions/DynamicPermissionManager.hpp\"\n#include \"../debug/HyprNotificationOverlay.hpp\"\n#include \"../plugins/PluginSystem.hpp\"\n\n#include \"../managers/input/trackpad/TrackpadGestures.hpp\"\n#include \"../managers/input/trackpad/gestures/DispatcherGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/ResizeGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/MoveGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/SpecialWorkspaceGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/CloseGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/FloatGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/FullscreenGesture.hpp\"\n#include \"../managers/input/trackpad/gestures/CursorZoomGesture.hpp\"\n\n#include \"../event/EventBus.hpp\"\n\n#include \"../protocols/types/ContentType.hpp\"\n#include <cstddef>\n#include <cstdint>\n#include <hyprutils/path/Path.hpp>\n#include <cstring>\n#include <string>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <glob.h>\n#include <xkbcommon/xkbcommon.h>\n\n#include <algorithm>\n#include <fstream>\n#include <iostream>\n#include <ranges>\n#include <unordered_set>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/string/ConstVarList.hpp>\n#include <filesystem>\n#include <memory>\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Animation;\nusing enum NContentType::eContentType;\n\n//NOLINTNEXTLINE\nextern \"C\" char** environ;\n\n#include \"ConfigDescriptions.hpp\"\n\nstatic Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) {\n    std::string V = VALUE;\n\n    if (!*data)\n        *data = new CGradientValueData();\n\n    const auto DATA = sc<CGradientValueData*>(*data);\n\n    CVarList2  varlist(std::string(V), 0, ' ');\n    DATA->m_colors.clear();\n\n    std::string parseError = \"\";\n\n    for (auto const& var : varlist) {\n        if (var.find(\"deg\") != std::string::npos) {\n            // last arg\n            try {\n                DATA->m_angle = std::stoi(std::string(var.substr(0, var.find(\"deg\")))) * (PI / 180.0); // radians\n            } catch (...) {\n                Log::logger->log(Log::WARN, \"Error parsing gradient {}\", V);\n                parseError = \"Error parsing gradient \" + V;\n            }\n\n            break;\n        }\n\n        if (DATA->m_colors.size() >= 10) {\n            Log::logger->log(Log::WARN, \"Error parsing gradient {}: max colors is 10.\", V);\n            parseError = \"Error parsing gradient \" + V + \": max colors is 10.\";\n            break;\n        }\n\n        try {\n            const auto COL = configStringToInt(std::string(var));\n            if (!COL)\n                throw std::runtime_error(std::format(\"failed to parse {} as a color\", var));\n            DATA->m_colors.emplace_back(COL.value());\n        } catch (std::exception& e) {\n            Log::logger->log(Log::WARN, \"Error parsing gradient {}\", V);\n            parseError = \"Error parsing gradient \" + V + \": \" + e.what();\n        }\n    }\n\n    if (DATA->m_colors.empty()) {\n        Log::logger->log(Log::WARN, \"Error parsing gradient {}\", V);\n        if (parseError.empty())\n            parseError = \"Error parsing gradient \" + V + \": No colors?\";\n\n        DATA->m_colors.emplace_back(0); // transparent\n    }\n\n    DATA->updateColorsOk();\n\n    Hyprlang::CParseResult result;\n    if (!parseError.empty())\n        result.setError(parseError.c_str());\n\n    return result;\n}\n\nstatic void configHandleGradientDestroy(void** data) {\n    if (*data)\n        delete sc<CGradientValueData*>(*data);\n}\n\nstatic Hyprlang::CParseResult configHandleGapSet(const char* VALUE, void** data) {\n    std::string V = VALUE;\n\n    if (!*data)\n        *data = new CCssGapData();\n\n    const auto             DATA = sc<CCssGapData*>(*data);\n    CVarList2              varlist((std::string(V)));\n    Hyprlang::CParseResult result;\n\n    try {\n        DATA->parseGapData(varlist);\n    } catch (...) {\n        std::string parseError = \"Error parsing gaps \" + V;\n        result.setError(parseError.c_str());\n    }\n\n    return result;\n}\n\nstatic void configHandleGapDestroy(void** data) {\n    if (*data)\n        delete sc<CCssGapData*>(*data);\n}\n\nstatic Hyprlang::CParseResult configHandleFontWeightSet(const char* VALUE, void** data) {\n    if (!*data)\n        *data = new CFontWeightConfigValueData();\n\n    const auto             DATA = sc<CFontWeightConfigValueData*>(*data);\n    Hyprlang::CParseResult result;\n\n    try {\n        DATA->parseWeight(VALUE);\n    } catch (...) {\n        std::string parseError = std::format(\"{} is not a valid font weight\", VALUE);\n        result.setError(parseError.c_str());\n    }\n\n    return result;\n}\n\nstatic void configHandleFontWeightDestroy(void** data) {\n    if (*data)\n        delete sc<CFontWeightConfigValueData*>(*data);\n}\n\nstatic Hyprlang::CParseResult handleExec(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleExec(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleRawExec(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleRawExec(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleExecOnce(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleExecOnce(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleExecRawOnce(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleExecRawOnce(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleExecShutdown(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleExecShutdown(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleMonitor(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleMonitor(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleBezier(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleAnimation(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleBind(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleBind(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleUnbind(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleUnbind(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleWorkspaceRules(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleWorkspaceRules(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleSubmap(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleSubmap(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleSource(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleSource(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleEnv(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleEnv(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handlePlugin(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handlePlugin(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handlePermission(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handlePermission(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleGesture(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleGesture(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleWindowrule(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleWindowrulev2(const char* c, const char* v) {\n    Hyprlang::CParseResult res;\n    res.setError(\"windowrulev2 is deprecated. Correct syntax can be found on the wiki.\");\n    return res;\n}\n\nstatic Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) {\n    const std::string      VALUE   = v;\n    const std::string      COMMAND = c;\n\n    const auto             RESULT = g_pConfigManager->handleLayerrule(COMMAND, VALUE);\n\n    Hyprlang::CParseResult result;\n    if (RESULT.has_value())\n        result.setError(RESULT.value().c_str());\n    return result;\n}\n\nstatic Hyprlang::CParseResult handleLayerrulev2(const char* c, const char* v) {\n    Hyprlang::CParseResult res;\n    res.setError(\"layerrulev2 doesn't exist. Correct syntax can be found on the wiki.\");\n    return res;\n}\n\nvoid CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) {\n    m_configValueNumber++;\n    m_config->addConfigValue(name, val);\n}\n\nvoid CConfigManager::registerConfigVar(const char* name, const Hyprlang::FLOAT& val) {\n    m_configValueNumber++;\n    m_config->addConfigValue(name, val);\n}\n\nvoid CConfigManager::registerConfigVar(const char* name, const Hyprlang::VEC2& val) {\n    m_configValueNumber++;\n    m_config->addConfigValue(name, val);\n}\n\nvoid CConfigManager::registerConfigVar(const char* name, const Hyprlang::STRING& val) {\n    m_configValueNumber++;\n    m_config->addConfigValue(name, val);\n}\n\nvoid CConfigManager::registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val) {\n    m_configValueNumber++;\n    m_config->addConfigValue(name, std::move(val));\n}\n\nCConfigManager::CConfigManager() {\n    const auto ERR = verifyConfigExists();\n\n    m_configPaths.emplace_back(getMainConfigPath());\n    m_config = makeUnique<Hyprlang::CConfig>(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true});\n\n    registerConfigVar(\"general:border_size\", Hyprlang::INT{1});\n    registerConfigVar(\"general:gaps_in\", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, \"5\"});\n    registerConfigVar(\"general:gaps_out\", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, \"20\"});\n    registerConfigVar(\"general:float_gaps\", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, \"0\"});\n    registerConfigVar(\"general:gaps_workspaces\", Hyprlang::INT{0});\n    registerConfigVar(\"general:no_focus_fallback\", Hyprlang::INT{0});\n    registerConfigVar(\"general:resize_on_border\", Hyprlang::INT{0});\n    registerConfigVar(\"general:extend_border_grab_area\", Hyprlang::INT{15});\n    registerConfigVar(\"general:hover_icon_on_border\", Hyprlang::INT{1});\n    registerConfigVar(\"general:layout\", {\"dwindle\"});\n    registerConfigVar(\"general:allow_tearing\", Hyprlang::INT{0});\n    registerConfigVar(\"general:resize_corner\", Hyprlang::INT{0});\n    registerConfigVar(\"general:snap:enabled\", Hyprlang::INT{0});\n    registerConfigVar(\"general:snap:window_gap\", Hyprlang::INT{10});\n    registerConfigVar(\"general:snap:monitor_gap\", Hyprlang::INT{10});\n    registerConfigVar(\"general:snap:border_overlap\", Hyprlang::INT{0});\n    registerConfigVar(\"general:snap:respect_gaps\", Hyprlang::INT{0});\n    registerConfigVar(\"general:col.active_border\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0xffffffff\"});\n    registerConfigVar(\"general:col.inactive_border\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0xff444444\"});\n    registerConfigVar(\"general:col.nogroup_border\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0xffffaaff\"});\n    registerConfigVar(\"general:col.nogroup_border_active\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0xffff00ff\"});\n    registerConfigVar(\"general:modal_parent_blocking\", Hyprlang::INT{1});\n    registerConfigVar(\"general:locale\", {\"\"});\n\n    registerConfigVar(\"misc:disable_hyprland_logo\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:disable_splash_rendering\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:col.splash\", Hyprlang::INT{0x55ffffff});\n    registerConfigVar(\"misc:splash_font_family\", {STRVAL_EMPTY});\n    registerConfigVar(\"misc:font_family\", {\"Sans\"});\n    registerConfigVar(\"misc:force_default_wallpaper\", Hyprlang::INT{-1});\n    registerConfigVar(\"misc:vfr\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:vrr\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:mouse_move_enables_dpms\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:key_press_enables_dpms\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:name_vk_after_proc\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:always_follow_on_dnd\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:layers_hog_keyboard_focus\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:animate_manual_resizes\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:animate_mouse_windowdragging\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:disable_autoreload\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:enable_swallow\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:swallow_regex\", {STRVAL_EMPTY});\n    registerConfigVar(\"misc:swallow_exception_regex\", {STRVAL_EMPTY});\n    registerConfigVar(\"misc:focus_on_activate\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:mouse_move_focuses_monitor\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:allow_session_lock_restore\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:session_lock_xray\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:close_special_on_empty\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:background_color\", Hyprlang::INT{0xff111111});\n    registerConfigVar(\"misc:on_focus_under_fullscreen\", Hyprlang::INT{2});\n    registerConfigVar(\"misc:exit_window_retains_fullscreen\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:initial_workspace_tracking\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:middle_click_paste\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:render_unfocused_fps\", Hyprlang::INT{15});\n    registerConfigVar(\"misc:disable_xdg_env_checks\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:disable_hyprland_guiutils_check\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:disable_watchdog_warning\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:lockdead_screen_delay\", Hyprlang::INT{1000});\n    registerConfigVar(\"misc:enable_anr_dialog\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:anr_missed_pings\", Hyprlang::INT{5});\n    registerConfigVar(\"misc:screencopy_force_8b\", Hyprlang::INT{1});\n    registerConfigVar(\"misc:disable_scale_notification\", Hyprlang::INT{0});\n    registerConfigVar(\"misc:size_limits_tiled\", Hyprlang::INT{0});\n\n    registerConfigVar(\"group:insert_after_current\", Hyprlang::INT{1});\n    registerConfigVar(\"group:focus_removed_window\", Hyprlang::INT{1});\n    registerConfigVar(\"group:merge_groups_on_drag\", Hyprlang::INT{1});\n    registerConfigVar(\"group:merge_groups_on_groupbar\", Hyprlang::INT{1});\n    registerConfigVar(\"group:merge_floated_into_tiled_on_groupbar\", Hyprlang::INT{0});\n    registerConfigVar(\"group:auto_group\", Hyprlang::INT{1});\n    registerConfigVar(\"group:drag_into_group\", Hyprlang::INT{1});\n    registerConfigVar(\"group:group_on_movetoworkspace\", Hyprlang::INT{0});\n    registerConfigVar(\"group:groupbar:enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:font_family\", {STRVAL_EMPTY});\n    registerConfigVar(\"group:groupbar:font_weight_active\", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, \"normal\"});\n    registerConfigVar(\"group:groupbar:font_weight_inactive\", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, \"normal\"});\n    registerConfigVar(\"group:groupbar:font_size\", Hyprlang::INT{8});\n    registerConfigVar(\"group:groupbar:gradients\", Hyprlang::INT{0});\n    registerConfigVar(\"group:groupbar:height\", Hyprlang::INT{14});\n    registerConfigVar(\"group:groupbar:indicator_gap\", Hyprlang::INT{0});\n    registerConfigVar(\"group:groupbar:indicator_height\", Hyprlang::INT{3});\n    registerConfigVar(\"group:groupbar:priority\", Hyprlang::INT{3});\n    registerConfigVar(\"group:groupbar:render_titles\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:scrolling\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:text_color\", Hyprlang::INT{0xffffffff});\n    registerConfigVar(\"group:groupbar:text_color_inactive\", Hyprlang::INT{-1});\n    registerConfigVar(\"group:groupbar:text_color_locked_active\", Hyprlang::INT{-1});\n    registerConfigVar(\"group:groupbar:text_color_locked_inactive\", Hyprlang::INT{-1});\n    registerConfigVar(\"group:groupbar:stacked\", Hyprlang::INT{0});\n    registerConfigVar(\"group:groupbar:rounding\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:rounding_power\", {2.F});\n    registerConfigVar(\"group:groupbar:gradient_rounding\", Hyprlang::INT{2});\n    registerConfigVar(\"group:groupbar:gradient_rounding_power\", {2.F});\n    registerConfigVar(\"group:groupbar:round_only_edges\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:gradient_round_only_edges\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:gaps_out\", Hyprlang::INT{2});\n    registerConfigVar(\"group:groupbar:gaps_in\", Hyprlang::INT{2});\n    registerConfigVar(\"group:groupbar:keep_upper_gap\", Hyprlang::INT{1});\n    registerConfigVar(\"group:groupbar:text_offset\", Hyprlang::INT{0});\n    registerConfigVar(\"group:groupbar:text_padding\", Hyprlang::INT{0});\n    registerConfigVar(\"group:groupbar:blur\", Hyprlang::INT{0});\n\n    registerConfigVar(\"debug:log_damage\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:overlay\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:damage_blink\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:pass\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:gl_debugging\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:disable_logs\", Hyprlang::INT{1});\n    registerConfigVar(\"debug:disable_time\", Hyprlang::INT{1});\n    registerConfigVar(\"debug:enable_stdout_logs\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:damage_tracking\", {sc<Hyprlang::INT>(DAMAGE_TRACKING_FULL)});\n    registerConfigVar(\"debug:manual_crash\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:suppress_errors\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:error_limit\", Hyprlang::INT{5});\n    registerConfigVar(\"debug:error_position\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:disable_scale_checks\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:colored_stdout_logs\", Hyprlang::INT{1});\n    registerConfigVar(\"debug:full_cm_proto\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:ds_handle_same_buffer\", Hyprlang::INT{1});\n    registerConfigVar(\"debug:ds_handle_same_buffer_fifo\", Hyprlang::INT{1});\n    registerConfigVar(\"debug:fifo_pending_workaround\", Hyprlang::INT{0});\n    registerConfigVar(\"debug:render_solitary_wo_damage\", Hyprlang::INT{0});\n\n    registerConfigVar(\"decoration:rounding\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:rounding_power\", {2.F});\n    registerConfigVar(\"decoration:blur:enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:blur:size\", Hyprlang::INT{8});\n    registerConfigVar(\"decoration:blur:passes\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:blur:ignore_opacity\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:blur:new_optimizations\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:blur:xray\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:blur:contrast\", {0.8916F});\n    registerConfigVar(\"decoration:blur:brightness\", {1.0F});\n    registerConfigVar(\"decoration:blur:vibrancy\", {0.1696F});\n    registerConfigVar(\"decoration:blur:vibrancy_darkness\", {0.0F});\n    registerConfigVar(\"decoration:blur:noise\", {0.0117F});\n    registerConfigVar(\"decoration:blur:special\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:blur:popups\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:blur:popups_ignorealpha\", {0.2F});\n    registerConfigVar(\"decoration:blur:input_methods\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:blur:input_methods_ignorealpha\", {0.2F});\n    registerConfigVar(\"decoration:active_opacity\", {1.F});\n    registerConfigVar(\"decoration:inactive_opacity\", {1.F});\n    registerConfigVar(\"decoration:fullscreen_opacity\", {1.F});\n    registerConfigVar(\"decoration:shadow:enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:shadow:range\", Hyprlang::INT{4});\n    registerConfigVar(\"decoration:shadow:render_power\", Hyprlang::INT{3});\n    registerConfigVar(\"decoration:shadow:ignore_window\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:shadow:offset\", Hyprlang::VEC2{0, 0});\n    registerConfigVar(\"decoration:shadow:scale\", {1.f});\n    registerConfigVar(\"decoration:shadow:sharp\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:shadow:color\", Hyprlang::INT{0xee1a1a1a});\n    registerConfigVar(\"decoration:shadow:color_inactive\", Hyprlang::INT{-1});\n    registerConfigVar(\"decoration:dim_inactive\", Hyprlang::INT{0});\n    registerConfigVar(\"decoration:dim_modal\", Hyprlang::INT{1});\n    registerConfigVar(\"decoration:dim_strength\", {0.5f});\n    registerConfigVar(\"decoration:dim_special\", {0.2f});\n    registerConfigVar(\"decoration:dim_around\", {0.4f});\n    registerConfigVar(\"decoration:screen_shader\", {STRVAL_EMPTY});\n    registerConfigVar(\"decoration:border_part_of_window\", Hyprlang::INT{1});\n\n    registerConfigVar(\"layout:single_window_aspect_ratio\", Hyprlang::VEC2{0, 0});\n    registerConfigVar(\"layout:single_window_aspect_ratio_tolerance\", {0.1f});\n\n    registerConfigVar(\"dwindle:pseudotile\", Hyprlang::INT{0});\n    registerConfigVar(\"dwindle:force_split\", Hyprlang::INT{0});\n    registerConfigVar(\"dwindle:permanent_direction_override\", Hyprlang::INT{0});\n    registerConfigVar(\"dwindle:preserve_split\", Hyprlang::INT{0});\n    registerConfigVar(\"dwindle:special_scale_factor\", {1.f});\n    registerConfigVar(\"dwindle:split_width_multiplier\", {1.0f});\n    registerConfigVar(\"dwindle:use_active_for_splits\", Hyprlang::INT{1});\n    registerConfigVar(\"dwindle:default_split_ratio\", {1.f});\n    registerConfigVar(\"dwindle:split_bias\", Hyprlang::INT{0});\n    registerConfigVar(\"dwindle:smart_split\", Hyprlang::INT{0});\n    registerConfigVar(\"dwindle:smart_resizing\", Hyprlang::INT{1});\n    registerConfigVar(\"dwindle:precise_mouse_move\", Hyprlang::INT{0});\n\n    registerConfigVar(\"master:special_scale_factor\", {1.f});\n    registerConfigVar(\"master:mfact\", {0.55f});\n    registerConfigVar(\"master:new_status\", {\"slave\"});\n    registerConfigVar(\"master:slave_count_for_center_master\", Hyprlang::INT{2});\n    registerConfigVar(\"master:center_master_fallback\", {\"left\"});\n    registerConfigVar(\"master:center_ignores_reserved\", Hyprlang::INT{0});\n    registerConfigVar(\"master:new_on_active\", {\"none\"});\n    registerConfigVar(\"master:new_on_top\", Hyprlang::INT{0});\n    registerConfigVar(\"master:orientation\", {\"left\"});\n    registerConfigVar(\"master:allow_small_split\", Hyprlang::INT{0});\n    registerConfigVar(\"master:smart_resizing\", Hyprlang::INT{1});\n    registerConfigVar(\"master:drop_at_cursor\", Hyprlang::INT{1});\n    registerConfigVar(\"master:always_keep_position\", Hyprlang::INT{0});\n\n    registerConfigVar(\"scrolling:fullscreen_on_one_column\", Hyprlang::INT{1});\n    registerConfigVar(\"scrolling:column_width\", Hyprlang::FLOAT{0.5F});\n    registerConfigVar(\"scrolling:focus_fit_method\", Hyprlang::INT{1});\n    registerConfigVar(\"scrolling:follow_focus\", Hyprlang::INT{1});\n    registerConfigVar(\"scrolling:follow_min_visible\", Hyprlang::FLOAT{0.4});\n    registerConfigVar(\"scrolling:explicit_column_widths\", Hyprlang::STRING{\"0.333, 0.5, 0.667, 1.0\"});\n    registerConfigVar(\"scrolling:direction\", Hyprlang::STRING{\"right\"});\n    registerConfigVar(\"scrolling:wrap_focus\", Hyprlang::INT{1});\n    registerConfigVar(\"scrolling:wrap_swapcol\", Hyprlang::INT{1});\n\n    registerConfigVar(\"animations:enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"animations:workspace_wraparound\", Hyprlang::INT{0});\n\n    registerConfigVar(\"input:follow_mouse\", Hyprlang::INT{1});\n    registerConfigVar(\"input:follow_mouse_threshold\", Hyprlang::FLOAT{0});\n    registerConfigVar(\"input:focus_on_close\", Hyprlang::INT{0});\n    registerConfigVar(\"input:mouse_refocus\", Hyprlang::INT{1});\n    registerConfigVar(\"input:special_fallthrough\", Hyprlang::INT{0});\n    registerConfigVar(\"input:off_window_axis_events\", Hyprlang::INT{1});\n    registerConfigVar(\"input:sensitivity\", {0.f});\n    registerConfigVar(\"input:accel_profile\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:rotation\", Hyprlang::INT{0});\n    registerConfigVar(\"input:kb_file\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:kb_layout\", {\"us\"});\n    registerConfigVar(\"input:kb_variant\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:kb_options\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:kb_rules\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:kb_model\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:repeat_rate\", Hyprlang::INT{25});\n    registerConfigVar(\"input:repeat_delay\", Hyprlang::INT{600});\n    registerConfigVar(\"input:natural_scroll\", Hyprlang::INT{0});\n    registerConfigVar(\"input:numlock_by_default\", Hyprlang::INT{0});\n    registerConfigVar(\"input:resolve_binds_by_sym\", Hyprlang::INT{0});\n    registerConfigVar(\"input:force_no_accel\", Hyprlang::INT{0});\n    registerConfigVar(\"input:float_switch_override_focus\", Hyprlang::INT{1});\n    registerConfigVar(\"input:left_handed\", Hyprlang::INT{0});\n    registerConfigVar(\"input:scroll_method\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:scroll_button\", Hyprlang::INT{0});\n    registerConfigVar(\"input:scroll_button_lock\", Hyprlang::INT{0});\n    registerConfigVar(\"input:scroll_factor\", {1.f});\n    registerConfigVar(\"input:scroll_points\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:emulate_discrete_scroll\", Hyprlang::INT{1});\n    registerConfigVar(\"input:touchpad:natural_scroll\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchpad:disable_while_typing\", Hyprlang::INT{1});\n    registerConfigVar(\"input:touchpad:clickfinger_behavior\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchpad:tap_button_map\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:touchpad:middle_button_emulation\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchpad:tap-to-click\", Hyprlang::INT{1});\n    registerConfigVar(\"input:touchpad:tap-and-drag\", Hyprlang::INT{1});\n    registerConfigVar(\"input:touchpad:drag_lock\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchpad:scroll_factor\", {1.f});\n    registerConfigVar(\"input:touchpad:flip_x\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchpad:flip_y\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchpad:drag_3fg\", Hyprlang::INT{0});\n    registerConfigVar(\"input:touchdevice:transform\", Hyprlang::INT{-1});\n    registerConfigVar(\"input:touchdevice:output\", {\"[[Auto]]\"});\n    registerConfigVar(\"input:touchdevice:enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"input:virtualkeyboard:share_states\", Hyprlang::INT{2});\n    registerConfigVar(\"input:virtualkeyboard:release_pressed_on_close\", Hyprlang::INT{0});\n    registerConfigVar(\"input:tablet:transform\", Hyprlang::INT{0});\n    registerConfigVar(\"input:tablet:output\", {STRVAL_EMPTY});\n    registerConfigVar(\"input:tablet:region_position\", Hyprlang::VEC2{0, 0});\n    registerConfigVar(\"input:tablet:absolute_region_position\", Hyprlang::INT{0});\n    registerConfigVar(\"input:tablet:region_size\", Hyprlang::VEC2{0, 0});\n    registerConfigVar(\"input:tablet:relative_input\", Hyprlang::INT{0});\n    registerConfigVar(\"input:tablet:left_handed\", Hyprlang::INT{0});\n    registerConfigVar(\"input:tablet:active_area_position\", Hyprlang::VEC2{0, 0});\n    registerConfigVar(\"input:tablet:active_area_size\", Hyprlang::VEC2{0, 0});\n\n    registerConfigVar(\"binds:pass_mouse_when_bound\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:scroll_event_delay\", Hyprlang::INT{300});\n    registerConfigVar(\"binds:workspace_back_and_forth\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:hide_special_on_workspace_change\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:allow_workspace_cycles\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:workspace_center_on\", Hyprlang::INT{1});\n    registerConfigVar(\"binds:focus_preferred_method\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:ignore_group_lock\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:movefocus_cycles_fullscreen\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:movefocus_cycles_groupfirst\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:disable_keybind_grabbing\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:allow_pin_fullscreen\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:drag_threshold\", Hyprlang::INT{0});\n    registerConfigVar(\"binds:window_direction_monitor_fallback\", Hyprlang::INT{1});\n\n    registerConfigVar(\"gestures:workspace_swipe_distance\", Hyprlang::INT{300});\n    registerConfigVar(\"gestures:workspace_swipe_invert\", Hyprlang::INT{1});\n    registerConfigVar(\"gestures:workspace_swipe_min_speed_to_force\", Hyprlang::INT{30});\n    registerConfigVar(\"gestures:workspace_swipe_cancel_ratio\", {0.5f});\n    registerConfigVar(\"gestures:workspace_swipe_create_new\", Hyprlang::INT{1});\n    registerConfigVar(\"gestures:workspace_swipe_direction_lock\", Hyprlang::INT{1});\n    registerConfigVar(\"gestures:workspace_swipe_direction_lock_threshold\", Hyprlang::INT{10});\n    registerConfigVar(\"gestures:workspace_swipe_forever\", Hyprlang::INT{0});\n    registerConfigVar(\"gestures:workspace_swipe_use_r\", Hyprlang::INT{0});\n    registerConfigVar(\"gestures:workspace_swipe_touch\", Hyprlang::INT{0});\n    registerConfigVar(\"gestures:workspace_swipe_touch_invert\", Hyprlang::INT{0});\n    registerConfigVar(\"gestures:close_max_timeout\", Hyprlang::INT{1000});\n\n    registerConfigVar(\"xwayland:enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"xwayland:use_nearest_neighbor\", Hyprlang::INT{1});\n    registerConfigVar(\"xwayland:force_zero_scaling\", Hyprlang::INT{0});\n    registerConfigVar(\"xwayland:create_abstract_socket\", Hyprlang::INT{0});\n\n    registerConfigVar(\"opengl:nvidia_anti_flicker\", Hyprlang::INT{1});\n\n    registerConfigVar(\"cursor:invisible\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:no_hardware_cursors\", Hyprlang::INT{2});\n    registerConfigVar(\"cursor:no_break_fs_vrr\", Hyprlang::INT{2});\n    registerConfigVar(\"cursor:min_refresh_rate\", Hyprlang::INT{24});\n    registerConfigVar(\"cursor:hotspot_padding\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:inactive_timeout\", {0.f});\n    registerConfigVar(\"cursor:no_warps\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:persistent_warps\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:warp_on_change_workspace\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:warp_on_toggle_special\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:default_monitor\", {STRVAL_EMPTY});\n    registerConfigVar(\"cursor:zoom_factor\", {1.f});\n    registerConfigVar(\"cursor:zoom_rigid\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:zoom_disable_aa\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:zoom_detached_camera\", Hyprlang::INT{1});\n    registerConfigVar(\"cursor:enable_hyprcursor\", Hyprlang::INT{1});\n    registerConfigVar(\"cursor:sync_gsettings_theme\", Hyprlang::INT{1});\n    registerConfigVar(\"cursor:hide_on_key_press\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:hide_on_touch\", Hyprlang::INT{1});\n    registerConfigVar(\"cursor:hide_on_tablet\", Hyprlang::INT{0});\n    registerConfigVar(\"cursor:use_cpu_buffer\", Hyprlang::INT{2});\n    registerConfigVar(\"cursor:warp_back_after_non_mouse_input\", Hyprlang::INT{0});\n\n    registerConfigVar(\"autogenerated\", Hyprlang::INT{0});\n\n    registerConfigVar(\"group:col.border_active\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66ffff00\"});\n    registerConfigVar(\"group:col.border_inactive\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66777700\"});\n    registerConfigVar(\"group:col.border_locked_active\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66ff5500\"});\n    registerConfigVar(\"group:col.border_locked_inactive\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66775500\"});\n\n    registerConfigVar(\"group:groupbar:col.active\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66ffff00\"});\n    registerConfigVar(\"group:groupbar:col.inactive\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66777700\"});\n    registerConfigVar(\"group:groupbar:col.locked_active\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66ff5500\"});\n    registerConfigVar(\"group:groupbar:col.locked_inactive\", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, \"0x66775500\"});\n\n    registerConfigVar(\"render:direct_scanout\", Hyprlang::INT{0});\n    registerConfigVar(\"render:expand_undersized_textures\", Hyprlang::INT{1});\n    registerConfigVar(\"render:xp_mode\", Hyprlang::INT{0});\n    registerConfigVar(\"render:ctm_animation\", Hyprlang::INT{2});\n    registerConfigVar(\"render:cm_fs_passthrough\", Hyprlang::INT{2});\n    registerConfigVar(\"render:cm_enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"render:send_content_type\", Hyprlang::INT{1});\n    registerConfigVar(\"render:cm_auto_hdr\", Hyprlang::INT{1});\n    registerConfigVar(\"render:new_render_scheduling\", Hyprlang::INT{0});\n    registerConfigVar(\"render:non_shader_cm\", Hyprlang::INT{3});\n    registerConfigVar(\"render:cm_sdr_eotf\", {\"default\"});\n    registerConfigVar(\"render:commit_timing_enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"render:icc_vcgt_enabled\", Hyprlang::INT{1});\n    registerConfigVar(\"render:use_shader_blur_blend\", Hyprlang::INT{0});\n\n    registerConfigVar(\"ecosystem:no_update_news\", Hyprlang::INT{0});\n    registerConfigVar(\"ecosystem:no_donation_nag\", Hyprlang::INT{0});\n    registerConfigVar(\"ecosystem:enforce_permissions\", Hyprlang::INT{0});\n\n    registerConfigVar(\"quirks:prefer_hdr\", Hyprlang::INT{0});\n    registerConfigVar(\"quirks:skip_non_kms_dmabuf_formats\", Hyprlang::INT{0});\n\n    // devices\n    m_config->addSpecialCategory(\"device\", {\"name\"});\n    m_config->addSpecialConfigValue(\"device\", \"sensitivity\", {0.F});\n    m_config->addSpecialConfigValue(\"device\", \"accel_profile\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"rotation\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"kb_file\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"kb_layout\", {\"us\"});\n    m_config->addSpecialConfigValue(\"device\", \"kb_variant\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"kb_options\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"kb_rules\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"kb_model\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"repeat_rate\", Hyprlang::INT{25});\n    m_config->addSpecialConfigValue(\"device\", \"repeat_delay\", Hyprlang::INT{600});\n    m_config->addSpecialConfigValue(\"device\", \"natural_scroll\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"tap_button_map\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"numlock_by_default\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"resolve_binds_by_sym\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"disable_while_typing\", Hyprlang::INT{1});\n    m_config->addSpecialConfigValue(\"device\", \"clickfinger_behavior\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"middle_button_emulation\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"tap-to-click\", Hyprlang::INT{1});\n    m_config->addSpecialConfigValue(\"device\", \"tap-and-drag\", Hyprlang::INT{1});\n    m_config->addSpecialConfigValue(\"device\", \"drag_lock\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"left_handed\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"scroll_method\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"scroll_button\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"scroll_button_lock\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"device\", \"scroll_points\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"scroll_factor\", Hyprlang::FLOAT{-1});\n    m_config->addSpecialConfigValue(\"device\", \"transform\", Hyprlang::INT{-1});\n    m_config->addSpecialConfigValue(\"device\", \"output\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"device\", \"enabled\", Hyprlang::INT{1});                  // only for mice, touchpads, and touchdevices\n    m_config->addSpecialConfigValue(\"device\", \"region_position\", Hyprlang::VEC2{0, 0});      // only for tablets\n    m_config->addSpecialConfigValue(\"device\", \"absolute_region_position\", Hyprlang::INT{0}); // only for tablets\n    m_config->addSpecialConfigValue(\"device\", \"region_size\", Hyprlang::VEC2{0, 0});          // only for tablets\n    m_config->addSpecialConfigValue(\"device\", \"relative_input\", Hyprlang::INT{0});           // only for tablets\n    m_config->addSpecialConfigValue(\"device\", \"active_area_position\", Hyprlang::VEC2{0, 0}); // only for tablets\n    m_config->addSpecialConfigValue(\"device\", \"active_area_size\", Hyprlang::VEC2{0, 0});     // only for tablets\n    m_config->addSpecialConfigValue(\"device\", \"flip_x\", Hyprlang::INT{0});                   // only for touchpads\n    m_config->addSpecialConfigValue(\"device\", \"flip_y\", Hyprlang::INT{0});                   // only for touchpads\n    m_config->addSpecialConfigValue(\"device\", \"drag_3fg\", Hyprlang::INT{0});                 // only for touchpads\n    m_config->addSpecialConfigValue(\"device\", \"keybinds\", Hyprlang::INT{1});                 // enable/disable keybinds\n    m_config->addSpecialConfigValue(\"device\", \"share_states\", Hyprlang::INT{0});             // only for virtualkeyboards\n    m_config->addSpecialConfigValue(\"device\", \"release_pressed_on_close\", Hyprlang::INT{0}); // only for virtualkeyboards\n\n    m_config->addSpecialCategory(\"monitorv2\", {.key = \"output\"});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"disabled\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"mode\", {\"preferred\"});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"position\", {\"auto\"});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"scale\", {\"auto\"});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"addreserved\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"mirror\", {STRVAL_EMPTY});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"bitdepth\", {STRVAL_EMPTY}); // TODO use correct type\n    m_config->addSpecialConfigValue(\"monitorv2\", \"cm\", {\"auto\"});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"sdr_eotf\", {\"default\"});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"sdrbrightness\", Hyprlang::FLOAT{1.0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"sdrsaturation\", Hyprlang::FLOAT{1.0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"vrr\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"transform\", {STRVAL_EMPTY}); // TODO use correct type\n    m_config->addSpecialConfigValue(\"monitorv2\", \"supports_wide_color\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"supports_hdr\", Hyprlang::INT{0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"sdr_min_luminance\", Hyprlang::FLOAT{0.2});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"sdr_max_luminance\", Hyprlang::INT{80});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"min_luminance\", Hyprlang::FLOAT{-1.0});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"max_luminance\", Hyprlang::INT{-1});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"max_avg_luminance\", Hyprlang::INT{-1});\n    m_config->addSpecialConfigValue(\"monitorv2\", \"icc\", Hyprlang::STRING{\"\"});\n\n    // windowrule v3\n    m_config->addSpecialCategory(\"windowrule\", {.key = \"name\"});\n    m_config->addSpecialConfigValue(\"windowrule\", \"enable\", Hyprlang::INT{1});\n\n    // layerrule v2\n    m_config->addSpecialCategory(\"layerrule\", {.key = \"name\"});\n    m_config->addSpecialConfigValue(\"layerrule\", \"enable\", Hyprlang::INT{1});\n\n    reloadRuleConfigs();\n\n    // keywords\n    m_config->registerHandler(&::handleExec, \"exec\", {false});\n    m_config->registerHandler(&::handleRawExec, \"execr\", {false});\n    m_config->registerHandler(&::handleExecOnce, \"exec-once\", {false});\n    m_config->registerHandler(&::handleExecRawOnce, \"execr-once\", {false});\n    m_config->registerHandler(&::handleExecShutdown, \"exec-shutdown\", {false});\n    m_config->registerHandler(&::handleMonitor, \"monitor\", {false});\n    m_config->registerHandler(&::handleBind, \"bind\", {true});\n    m_config->registerHandler(&::handleUnbind, \"unbind\", {false});\n    m_config->registerHandler(&::handleWorkspaceRules, \"workspace\", {false});\n    m_config->registerHandler(&::handleWindowrule, \"windowrule\", {false});\n    m_config->registerHandler(&::handleLayerrule, \"layerrule\", {false});\n    m_config->registerHandler(&::handleBezier, \"bezier\", {false});\n    m_config->registerHandler(&::handleAnimation, \"animation\", {false});\n    m_config->registerHandler(&::handleSource, \"source\", {false});\n    m_config->registerHandler(&::handleSubmap, \"submap\", {false});\n    m_config->registerHandler(&::handlePlugin, \"plugin\", {false});\n    m_config->registerHandler(&::handlePermission, \"permission\", {false});\n    m_config->registerHandler(&::handleGesture, \"gesture\", {true});\n    m_config->registerHandler(&::handleEnv, \"env\", {true});\n\n    // windowrulev2 and layerrulev2 errors\n    m_config->registerHandler(&::handleWindowrulev2, \"windowrulev2\", {false});\n    m_config->registerHandler(&::handleLayerrulev2, \"layerrulev2\", {false});\n\n    // pluginza\n    m_config->addSpecialCategory(\"plugin\", {nullptr, true});\n\n    m_config->commence();\n\n    resetHLConfig();\n\n    if (CONFIG_OPTIONS.size() != m_configValueNumber - 1 /* autogenerated is special */)\n        Log::logger->log(Log::DEBUG, \"Warning: config descriptions have {} entries, but there are {} config values. This should fail tests!!\", CONFIG_OPTIONS.size(),\n                         m_configValueNumber);\n\n    if (!g_pCompositor->m_onlyConfigVerification) {\n        Log::logger->log(\n            Log::DEBUG,\n            \"!!!!HEY YOU, YES YOU!!!!: further logs to stdout / logfile are disabled by default. BEFORE SENDING THIS LOG, ENABLE THEM. Use debug:disable_logs = false to do so: \"\n            \"https://wiki.hypr.land/Configuring/Variables/#debug\");\n    }\n\n    if (g_pEventLoopManager && ERR.has_value())\n        g_pEventLoopManager->doLater([ERR] { g_pHyprError->queueCreate(ERR.value(), CHyprColor{1.0, 0.1, 0.1, 1.0}); });\n}\n\nvoid CConfigManager::reloadRuleConfigs() {\n    // FIXME: this should also remove old values if they are removed\n\n    for (const auto& r : Desktop::Rule::allMatchPropStrings()) {\n        m_config->addSpecialConfigValue(\"windowrule\", (\"match:\" + r).c_str(), Hyprlang::STRING{\"\"});\n    }\n\n    for (const auto& r : Desktop::Rule::windowEffects()->allEffectStrings()) {\n        m_config->addSpecialConfigValue(\"windowrule\", r.c_str(), Hyprlang::STRING{\"\"});\n    }\n\n    for (const auto& r : Desktop::Rule::allMatchPropStrings()) {\n        m_config->addSpecialConfigValue(\"layerrule\", (\"match:\" + r).c_str(), Hyprlang::STRING{\"\"});\n    }\n\n    for (const auto& r : Desktop::Rule::layerEffects()->allEffectStrings()) {\n        m_config->addSpecialConfigValue(\"layerrule\", r.c_str(), Hyprlang::STRING{\"\"});\n    }\n}\n\nstd::optional<std::string> CConfigManager::generateConfig(std::string configPath, bool safeMode) {\n    std::string parentPath = std::filesystem::path(configPath).parent_path();\n\n    if (!parentPath.empty()) {\n        std::error_code ec;\n        bool            created = std::filesystem::create_directories(parentPath, ec);\n        if (ec) {\n            Log::logger->log(Log::ERR, \"Couldn't create config home directory ({}): {}\", ec.message(), parentPath);\n            return \"Config could not be generated.\";\n        }\n        if (created)\n            Log::logger->log(Log::WARN, \"Creating config home directory\");\n    }\n\n    Log::logger->log(Log::WARN, \"No config file found; attempting to generate.\");\n    std::ofstream ofs;\n    ofs.open(configPath, std::ios::trunc);\n    if (!safeMode) {\n        ofs << AUTOGENERATED_PREFIX;\n        ofs << EXAMPLE_CONFIG;\n    } else {\n        std::string n = std::string{EXAMPLE_CONFIG};\n        replaceInString(n, \"\\n$menu = hyprlauncher\\n\", \"\\n$menu = hyprland-run\\n\");\n        ofs << n;\n    }\n    ofs.close();\n\n    if (ofs.fail())\n        return \"Config could not be generated.\";\n\n    return configPath;\n}\n\nstd::string CConfigManager::getMainConfigPath() {\n    static bool lastSafeMode = g_pCompositor->m_safeMode;\n    static auto getCfgPath   = [this]() -> std::string {\n        lastSafeMode          = g_pCompositor->m_safeMode;\n        m_firstExecDispatched = false;\n\n        if (g_pCompositor->m_safeMode) {\n            const auto CONFIGPATH = g_pCompositor->m_instancePath + \"/recoverycfg.conf\";\n            return generateConfig(CONFIGPATH, false).value();\n        }\n\n        if (!g_pCompositor->m_explicitConfigPath.empty())\n            return g_pCompositor->m_explicitConfigPath;\n\n        if (const auto CFG_ENV = getenv(\"HYPRLAND_CONFIG\"); CFG_ENV)\n            return CFG_ENV;\n\n        const auto PATHS = Hyprutils::Path::findConfig(ISDEBUG ? \"hyprlandd\" : \"hyprland\");\n        if (PATHS.first.has_value()) {\n            return PATHS.first.value();\n        } else if (PATHS.second.has_value()) {\n            const auto CONFIGPATH = Hyprutils::Path::fullConfigPath(PATHS.second.value(), ISDEBUG ? \"hyprlandd\" : \"hyprland\");\n            return generateConfig(CONFIGPATH).value();\n        } else\n            throw std::runtime_error(\"Neither HOME nor XDG_CONFIG_HOME are set in the environment. Could not find config in XDG_CONFIG_DIRS or /etc/xdg.\");\n    };\n    static std::string CONFIG_PATH = getCfgPath();\n\n    if (lastSafeMode != g_pCompositor->m_safeMode) {\n        CONFIG_PATH = getCfgPath();\n        m_config->changeRootPath(CONFIG_PATH.c_str());\n    }\n\n    return CONFIG_PATH;\n}\n\nstd::optional<std::string> CConfigManager::verifyConfigExists() {\n    std::string mainConfigPath = getMainConfigPath();\n\n    if (!std::filesystem::exists(mainConfigPath))\n        return \"broken config dir?\";\n\n    return {};\n}\n\nstd::string CConfigManager::getConfigString() {\n    std::string configString;\n    std::string currFileContent;\n\n    for (const auto& path : m_configPaths) {\n        std::ifstream configFile(path);\n        configString += (\"\\n\\nConfig File: \" + path + \": \");\n        if (!configFile.is_open()) {\n            Log::logger->log(Log::DEBUG, \"Config file not readable/found!\");\n            configString += \"Read Failed\\n\";\n            continue;\n        }\n        configString += \"Read Succeeded\\n\";\n        currFileContent.assign(std::istreambuf_iterator<char>(configFile), std::istreambuf_iterator<char>());\n        configString.append(currFileContent);\n    }\n    return configString;\n}\n\nstd::string CConfigManager::getErrors() {\n    return m_configErrors;\n}\n\nstatic std::vector<const char*> HL_VERSION_VARS = {\n    \"HYPRLAND_V_0_53\",\n};\n\nstatic void exportHlVersionVars() {\n    for (const auto& v : HL_VERSION_VARS) {\n        setenv(v, \"1\", 1);\n    }\n}\n\nstatic void clearHlVersionVars() {\n    for (const auto& v : HL_VERSION_VARS) {\n        unsetenv(v);\n    }\n}\n\nvoid CConfigManager::reload() {\n    Event::bus()->m_events.config.preReload.emit();\n    setDefaultAnimationVars();\n    resetHLConfig();\n    m_configCurrentPath = getMainConfigPath();\n\n    exportHlVersionVars();\n\n    const auto ERR = m_config->parse();\n\n    clearHlVersionVars();\n\n    const auto monitorError               = handleMonitorv2();\n    const auto ruleError                  = reloadRules();\n    m_lastConfigVerificationWasSuccessful = !ERR.error && !monitorError.error;\n    postConfigReload(ERR.error || !monitorError.error ? ERR : monitorError);\n}\n\nstd::string CConfigManager::verify() {\n    setDefaultAnimationVars();\n    resetHLConfig();\n    m_configCurrentPath                   = getMainConfigPath();\n    const auto ERR                        = m_config->parse();\n    m_lastConfigVerificationWasSuccessful = !ERR.error;\n    if (ERR.error)\n        return ERR.getError();\n    return \"config ok\";\n}\n\nvoid CConfigManager::setDefaultAnimationVars() {\n    m_animationTree.createNode(\"__internal_fadeCTM\");\n    m_animationTree.createNode(\"global\");\n\n    // global\n    m_animationTree.createNode(\"windows\", \"global\");\n    m_animationTree.createNode(\"layers\", \"global\");\n    m_animationTree.createNode(\"fade\", \"global\");\n    m_animationTree.createNode(\"border\", \"global\");\n    m_animationTree.createNode(\"borderangle\", \"global\");\n    m_animationTree.createNode(\"workspaces\", \"global\");\n    m_animationTree.createNode(\"zoomFactor\", \"global\");\n    m_animationTree.createNode(\"monitorAdded\", \"global\");\n\n    // layer\n    m_animationTree.createNode(\"layersIn\", \"layers\");\n    m_animationTree.createNode(\"layersOut\", \"layers\");\n\n    // windows\n    m_animationTree.createNode(\"windowsIn\", \"windows\");\n    m_animationTree.createNode(\"windowsOut\", \"windows\");\n    m_animationTree.createNode(\"windowsMove\", \"windows\");\n\n    // fade\n    m_animationTree.createNode(\"fadeIn\", \"fade\");\n    m_animationTree.createNode(\"fadeOut\", \"fade\");\n    m_animationTree.createNode(\"fadeSwitch\", \"fade\");\n    m_animationTree.createNode(\"fadeShadow\", \"fade\");\n    m_animationTree.createNode(\"fadeDim\", \"fade\");\n    m_animationTree.createNode(\"fadeLayers\", \"fade\");\n    m_animationTree.createNode(\"fadeLayersIn\", \"fadeLayers\");\n    m_animationTree.createNode(\"fadeLayersOut\", \"fadeLayers\");\n    m_animationTree.createNode(\"fadePopups\", \"fade\");\n    m_animationTree.createNode(\"fadePopupsIn\", \"fadePopups\");\n    m_animationTree.createNode(\"fadePopupsOut\", \"fadePopups\");\n    m_animationTree.createNode(\"fadeDpms\", \"fade\");\n\n    // workspaces\n    m_animationTree.createNode(\"workspacesIn\", \"workspaces\");\n    m_animationTree.createNode(\"workspacesOut\", \"workspaces\");\n    m_animationTree.createNode(\"specialWorkspace\", \"workspaces\");\n    m_animationTree.createNode(\"specialWorkspaceIn\", \"specialWorkspace\");\n    m_animationTree.createNode(\"specialWorkspaceOut\", \"specialWorkspace\");\n\n    // init the root nodes\n    m_animationTree.setConfigForNode(\"global\", 1, 8.f, \"default\");\n    m_animationTree.setConfigForNode(\"__internal_fadeCTM\", 1, 5.f, \"linear\");\n    m_animationTree.setConfigForNode(\"borderangle\", 0, 1, \"default\");\n}\n\nstd::optional<std::string> CConfigManager::resetHLConfig() {\n    m_monitorRules.clear();\n    g_pKeybindManager->clearKeybinds();\n    g_pAnimationManager->removeAllBeziers();\n    g_pAnimationManager->addBezierWithName(\"linear\", Vector2D(0.0, 0.0), Vector2D(1.0, 1.0));\n    g_pTrackpadGestures->clearGestures();\n\n    m_workspaceRules.clear();\n    setDefaultAnimationVars(); // reset anims\n    m_declaredPlugins.clear();\n    m_failedPluginConfigValues.clear();\n    m_finalExecRequests.clear();\n    m_keywordRules.clear();\n\n    // paths\n    m_configPaths.clear();\n    std::string mainConfigPath = getMainConfigPath();\n    Log::logger->log(Log::DEBUG, \"Using config: {}\", mainConfigPath);\n    m_configPaths.emplace_back(mainConfigPath);\n\n    const auto RET = verifyConfigExists();\n\n    reloadRuleConfigs();\n\n    return RET;\n}\n\nvoid CConfigManager::updateWatcher() {\n    static const auto PDISABLEAUTORELOAD = CConfigValue<Hyprlang::INT>(\"misc:disable_autoreload\");\n    g_pConfigWatcher->setWatchList(*PDISABLEAUTORELOAD ? std::vector<std::string>{} : m_configPaths);\n}\n\nstd::optional<std::string> CConfigManager::handleMonitorv2(const std::string& output) {\n    auto parser = CMonitorRuleParser(output);\n    auto VAL    = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"disabled\", output.c_str());\n    if (VAL && VAL->m_bSetByUser && std::any_cast<Hyprlang::INT>(VAL->getValue()))\n        parser.setDisabled();\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"mode\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.parseMode(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"position\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.parsePosition(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"scale\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.parseScale(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"addreserved\", output.c_str());\n    if (VAL && VAL->m_bSetByUser) {\n        const auto ARGS = CVarList(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n        try {\n            // top, right, bottom, left\n            parser.setReserved({std::stoi(ARGS[0]), std::stoi(ARGS[3]), std::stoi(ARGS[1]), std::stoi(ARGS[2])});\n        } catch (...) { return \"parse error: invalid reserved area\"; }\n    }\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"mirror\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.setMirror(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"bitdepth\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.parseBitdepth(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"cm\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.parseCM(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"sdr_eotf\", output.c_str());\n    if (VAL && VAL->m_bSetByUser) {\n        const std::string value = std::any_cast<Hyprlang::STRING>(VAL->getValue());\n        // remap legacy\n        if (value == \"0\")\n            parser.rule().sdrEotf = NTransferFunction::TF_AUTO;\n        else if (value == \"1\")\n            parser.rule().sdrEotf = NTransferFunction::TF_SRGB;\n        else if (value == \"2\")\n            parser.rule().sdrEotf = NTransferFunction::TF_GAMMA22;\n        else\n            parser.rule().sdrEotf = NTransferFunction::fromString(value);\n    }\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"sdrbrightness\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().sdrBrightness = std::any_cast<Hyprlang::FLOAT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"sdrsaturation\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().sdrSaturation = std::any_cast<Hyprlang::FLOAT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"vrr\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().vrr = std::any_cast<Hyprlang::INT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"transform\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.parseTransform(std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"supports_wide_color\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().supportsWideColor = std::any_cast<Hyprlang::INT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"supports_hdr\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().supportsHDR = std::any_cast<Hyprlang::INT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"sdr_min_luminance\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().sdrMinLuminance = std::any_cast<Hyprlang::FLOAT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"sdr_max_luminance\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().sdrMaxLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue());\n\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"min_luminance\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().minLuminance = std::any_cast<Hyprlang::FLOAT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"max_luminance\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().maxLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue());\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"max_avg_luminance\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().maxAvgLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue());\n\n    VAL = m_config->getSpecialConfigValuePtr(\"monitorv2\", \"icc\", output.c_str());\n    if (VAL && VAL->m_bSetByUser)\n        parser.rule().iccFile = std::any_cast<Hyprlang::STRING>(VAL->getValue());\n\n    auto newrule = parser.rule();\n\n    std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; });\n\n    m_monitorRules.push_back(newrule);\n\n    return parser.getError();\n}\n\nHyprlang::CParseResult CConfigManager::handleMonitorv2() {\n    Hyprlang::CParseResult result;\n    for (const auto& output : m_config->listKeysForSpecialCategory(\"monitorv2\")) {\n        const auto error = handleMonitorv2(output);\n        if (error.has_value()) {\n            result.setError(error.value().c_str());\n            return result;\n        }\n    }\n    return result;\n}\n\nstd::optional<std::string> CConfigManager::addRuleFromConfigKey(const std::string& name) {\n    const auto ENABLED = m_config->getSpecialConfigValuePtr(\"windowrule\", \"enable\", name.c_str());\n    if (ENABLED && ENABLED->m_bSetByUser && std::any_cast<Hyprlang::INT>(ENABLED->getValue()) == 0)\n        return std::nullopt;\n\n    SP<Desktop::Rule::CWindowRule> rule = makeShared<Desktop::Rule::CWindowRule>(name);\n\n    for (const auto& r : Desktop::Rule::allMatchPropStrings()) {\n        auto VAL = m_config->getSpecialConfigValuePtr(\"windowrule\", (\"match:\" + r).c_str(), name.c_str());\n        if (VAL && VAL->m_bSetByUser)\n            rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    }\n\n    for (const auto& e : Desktop::Rule::windowEffects()->allEffectStrings()) {\n        auto VAL = m_config->getSpecialConfigValuePtr(\"windowrule\", e.c_str(), name.c_str());\n        if (VAL && VAL->m_bSetByUser)\n            rule->addEffect(Desktop::Rule::windowEffects()->get(e).value_or(Desktop::Rule::WINDOW_RULE_EFFECT_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    }\n\n    Desktop::Rule::ruleEngine()->registerRule(std::move(rule));\n    return std::nullopt;\n}\n\nstd::optional<std::string> CConfigManager::addLayerRuleFromConfigKey(const std::string& name) {\n\n    const auto ENABLED = m_config->getSpecialConfigValuePtr(\"layerrule\", \"enable\", name.c_str());\n    if (ENABLED && ENABLED->m_bSetByUser && std::any_cast<Hyprlang::INT>(ENABLED->getValue()) != 0)\n        return std::nullopt;\n\n    SP<Desktop::Rule::CLayerRule> rule = makeShared<Desktop::Rule::CLayerRule>(name);\n\n    for (const auto& r : Desktop::Rule::allMatchPropStrings()) {\n        auto VAL = m_config->getSpecialConfigValuePtr(\"layerrule\", (\"match:\" + r).c_str(), name.c_str());\n        if (VAL && VAL->m_bSetByUser)\n            rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    }\n\n    for (const auto& e : Desktop::Rule::layerEffects()->allEffectStrings()) {\n        auto VAL = m_config->getSpecialConfigValuePtr(\"layerrule\", e.c_str(), name.c_str());\n        if (VAL && VAL->m_bSetByUser)\n            rule->addEffect(Desktop::Rule::layerEffects()->get(e).value_or(Desktop::Rule::LAYER_RULE_EFFECT_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));\n    }\n\n    Desktop::Rule::ruleEngine()->registerRule(std::move(rule));\n    return std::nullopt;\n}\n\nHyprlang::CParseResult CConfigManager::reloadRules() {\n    Desktop::Rule::ruleEngine()->clearAllRules();\n\n    Hyprlang::CParseResult result;\n    for (const auto& name : m_config->listKeysForSpecialCategory(\"windowrule\")) {\n        const auto error = addRuleFromConfigKey(name);\n        if (error.has_value())\n            result.setError(error.value().c_str());\n    }\n    for (const auto& name : m_config->listKeysForSpecialCategory(\"layerrule\")) {\n        const auto error = addLayerRuleFromConfigKey(name);\n        if (error.has_value())\n            result.setError(error.value().c_str());\n    }\n\n    for (auto& rule : m_keywordRules) {\n        Desktop::Rule::ruleEngine()->registerRule(SP<Desktop::Rule::IRule>{rule});\n    }\n\n    Desktop::Rule::ruleEngine()->updateAllRules();\n\n    return result;\n}\n\nvoid CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {\n    updateWatcher();\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        w->uncacheWindowDecos();\n    }\n\n    static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>(\"cursor:zoom_factor\");\n    for (auto const& m : g_pCompositor->m_monitors) {\n        *(m->m_cursorZoom) = *PZOOMFACTOR;\n        if (m->m_activeWorkspace)\n            m->m_activeWorkspace->m_space->recalculate();\n    }\n\n    // Update the keyboard layout to the cfg'd one if this is not the first launch\n    if (!m_isFirstLaunch) {\n        g_pInputManager->setKeyboardLayout();\n        g_pInputManager->setPointerConfigs();\n        g_pInputManager->setTouchDeviceConfigs();\n        g_pInputManager->setTabletConfigs();\n\n        g_pHyprRenderer->m_reloadScreenShader = true;\n    }\n\n    // parseError will be displayed next frame\n\n    if (result.error)\n        m_configErrors = result.getError();\n    else\n        m_configErrors = \"\";\n\n    if (result.error && !std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"debug:suppress_errors\")))\n        g_pHyprError->queueCreate(result.getError(), CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0));\n    else if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"autogenerated\")) == 1)\n        g_pHyprError->queueCreate(\n            \"Warning: You're using an autogenerated config! Edit the config file to get rid of this message. (config file: \" + getMainConfigPath() +\n                \" )\\nSUPER+Q -> kitty (if it doesn't launch, make sure it's installed or choose a different terminal in the config)\\nSUPER+M -> exit Hyprland\",\n            CHyprColor(1.0, 1.0, 70.0 / 255.0, 1.0));\n    else\n        g_pHyprError->destroy();\n\n    // Set the modes for all monitors as we configured them\n    // not on first launch because monitors might not exist yet\n    // and they'll be taken care of in the newMonitor event\n    // ignore if nomonitorreload is set\n    if (!m_isFirstLaunch && !m_noMonitorReload) {\n        // check\n        performMonitorReload();\n        ensureMonitorStatus();\n        ensureVRR();\n    }\n\n#ifndef NO_XWAYLAND\n    const auto PENABLEXWAYLAND     = std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"xwayland:enabled\"));\n    g_pCompositor->m_wantsXwayland = PENABLEXWAYLAND;\n    // enable/disable xwayland usage\n    if (!m_isFirstLaunch &&\n        g_pXWayland /* XWayland has to be initialized by CCompositor::initManagers for this to make sense, and it doesn't have to be (e.g. very early plugin load) */) {\n        bool prevEnabledXwayland = g_pXWayland->enabled();\n        if (g_pCompositor->m_wantsXwayland != prevEnabledXwayland)\n            g_pXWayland = makeUnique<CXWayland>(g_pCompositor->m_wantsXwayland);\n    } else\n        g_pCompositor->m_wantsXwayland = PENABLEXWAYLAND;\n#endif\n\n    if (!m_isFirstLaunch && !g_pCompositor->m_unsafeState)\n        refreshGroupBarGradients();\n\n    // Updates dynamic window and workspace rules\n    for (auto const& w : g_pCompositor->getWorkspaces()) {\n        if (w->inert())\n            continue;\n        w->updateWindows();\n        w->updateWindowData();\n    }\n\n    // Update window border colors\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    // manual crash\n    if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"debug:manual_crash\")) && !m_manualCrashInitiated) {\n        m_manualCrashInitiated = true;\n        g_pHyprNotificationOverlay->addNotification(\"Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.\", CHyprColor(0), 5000,\n                                                    ICON_INFO);\n    } else if (m_manualCrashInitiated && !std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"debug:manual_crash\"))) {\n        // cowabunga it is\n        g_pHyprRenderer->initiateManualCrash();\n    }\n\n    auto disableStdout = !std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"debug:enable_stdout_logs\"));\n    if (disableStdout && m_isFirstLaunch)\n        Log::logger->log(Log::DEBUG, \"Disabling stdout logs! Check the log for further logs.\");\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        // mark blur dirty\n        m->m_blurFBDirty = true;\n\n        g_pCompositor->scheduleFrameForMonitor(m);\n\n        // Force the compositor to fully re-render all monitors\n        m->m_forceFullFrames = 2;\n\n        // also force mirrors, as the aspect ratio could've changed\n        for (auto const& mirror : m->m_mirrors)\n            mirror->m_forceFullFrames = 3;\n    }\n\n    // Reset no monitor reload\n    m_noMonitorReload = false;\n\n    // update plugins\n    handlePluginLoads();\n\n    // update persistent workspaces\n    if (!m_isFirstLaunch)\n        ensurePersistentWorkspacesPresent();\n\n    // update layouts\n    Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();\n\n    Event::bus()->m_events.config.reloaded.emit();\n    if (g_pEventManager)\n        g_pEventManager->postEvent(SHyprIPCEvent{\"configreloaded\", \"\"});\n}\n\nvoid CConfigManager::init() {\n\n    g_pConfigWatcher->setOnChange([this](const CConfigWatcher::SConfigWatchEvent& e) {\n        Log::logger->log(Log::DEBUG, \"CConfigManager: file {} modified, reloading\", e.file);\n        reload();\n    });\n\n    reload();\n\n    m_isFirstLaunch = false;\n}\n\nstd::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) {\n    const auto RET = m_config->parseDynamic(COMMAND.c_str(), VALUE.c_str());\n\n    // invalidate layouts if they changed\n    if (COMMAND == \"monitor\" || COMMAND.contains(\"gaps_\") || COMMAND.starts_with(\"dwindle:\") || COMMAND.starts_with(\"master:\")) {\n        for (auto const& m : g_pCompositor->m_monitors) {\n            g_layoutManager->recalculateMonitor(m);\n        }\n    }\n\n    // Update window border colors\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    // manual crash\n    if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"debug:manual_crash\")) && !m_manualCrashInitiated) {\n        m_manualCrashInitiated = true;\n        if (g_pHyprNotificationOverlay) {\n            g_pHyprNotificationOverlay->addNotification(\"Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.\", CHyprColor(0), 5000,\n                                                        ICON_INFO);\n        }\n    } else if (m_manualCrashInitiated && !std::any_cast<Hyprlang::INT>(m_config->getConfigValue(\"debug:manual_crash\"))) {\n        // cowabunga it is\n        g_pHyprRenderer->initiateManualCrash();\n    }\n\n    return RET.error ? RET.getError() : \"\";\n}\n\nHyprlang::CConfigValue* CConfigManager::getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback) {\n\n    const auto VAL = m_config->getSpecialConfigValuePtr(\"device\", val.c_str(), dev.c_str());\n\n    if ((!VAL || !VAL->m_bSetByUser) && !fallback.empty())\n        return m_config->getConfigValuePtr(fallback.c_str());\n\n    return VAL;\n}\n\nbool CConfigManager::deviceConfigExplicitlySet(const std::string& dev, const std::string& val) {\n    const auto VAL = m_config->getSpecialConfigValuePtr(\"device\", val.c_str(), dev.c_str());\n\n    return VAL && VAL->m_bSetByUser;\n}\n\nint CConfigManager::getDeviceInt(const std::string& dev, const std::string& v, const std::string& fallback) {\n    return std::any_cast<Hyprlang::INT>(getConfigValueSafeDevice(dev, v, fallback)->getValue());\n}\n\nfloat CConfigManager::getDeviceFloat(const std::string& dev, const std::string& v, const std::string& fallback) {\n    return std::any_cast<Hyprlang::FLOAT>(getConfigValueSafeDevice(dev, v, fallback)->getValue());\n}\n\nVector2D CConfigManager::getDeviceVec(const std::string& dev, const std::string& v, const std::string& fallback) {\n    auto vec = std::any_cast<Hyprlang::VEC2>(getConfigValueSafeDevice(dev, v, fallback)->getValue());\n    return {vec.x, vec.y};\n}\n\nstd::string CConfigManager::getDeviceString(const std::string& dev, const std::string& v, const std::string& fallback) {\n    auto VAL = std::string{std::any_cast<Hyprlang::STRING>(getConfigValueSafeDevice(dev, v, fallback)->getValue())};\n\n    if (VAL == STRVAL_EMPTY)\n        return \"\";\n\n    return VAL;\n}\n\nSMonitorRule CConfigManager::getMonitorRuleFor(const PHLMONITOR PMONITOR) {\n    auto applyWlrOutputConfig = [PMONITOR](SMonitorRule rule) -> SMonitorRule {\n        const auto CONFIG = PROTO::outputManagement->getOutputStateFor(PMONITOR);\n\n        if (!CONFIG)\n            return rule;\n\n        Log::logger->log(Log::DEBUG, \"CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}\", PMONITOR->m_name);\n\n        Log::logger->log(Log::DEBUG, \" > overriding enabled: {} -> {}\", !rule.disabled, !CONFIG->enabled);\n        rule.disabled = !CONFIG->enabled;\n\n        if ((CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_MODE) || (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE)) {\n            Log::logger->log(Log::DEBUG, \" > overriding mode: {:.0f}x{:.0f}@{:.2f}Hz -> {:.0f}x{:.0f}@{:.2f}Hz\", rule.resolution.x, rule.resolution.y, rule.refreshRate,\n                             CONFIG->resolution.x, CONFIG->resolution.y, CONFIG->refresh / 1000.F);\n            rule.resolution  = CONFIG->resolution;\n            rule.refreshRate = CONFIG->refresh / 1000.F;\n        }\n\n        if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) {\n            Log::logger->log(Log::DEBUG, \" > overriding offset: {:.0f}, {:.0f} -> {:.0f}, {:.0f}\", rule.offset.x, rule.offset.y, CONFIG->position.x, CONFIG->position.y);\n            rule.offset = CONFIG->position;\n        }\n\n        if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) {\n            Log::logger->log(Log::DEBUG, \" > overriding transform: {} -> {}\", sc<uint8_t>(rule.transform), sc<uint8_t>(CONFIG->transform));\n            rule.transform = CONFIG->transform;\n        }\n\n        if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) {\n            Log::logger->log(Log::DEBUG, \" > overriding scale: {} -> {}\", sc<uint8_t>(rule.scale), sc<uint8_t>(CONFIG->scale));\n            rule.scale = CONFIG->scale;\n        }\n\n        if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) {\n            Log::logger->log(Log::DEBUG, \" > overriding vrr: {} -> {}\", rule.vrr.value_or(0), CONFIG->adaptiveSync);\n            rule.vrr = sc<int>(CONFIG->adaptiveSync);\n        }\n\n        return rule;\n    };\n\n    for (auto const& r : m_monitorRules | std::views::reverse) {\n        if (PMONITOR->matchesStaticSelector(r.name)) {\n            return applyWlrOutputConfig(r);\n        }\n    }\n\n    Log::logger->log(Log::WARN, \"No rule found for {}, trying to use the first.\", PMONITOR->m_name);\n\n    for (auto const& r : m_monitorRules) {\n        if (r.name.empty()) {\n            return applyWlrOutputConfig(r);\n        }\n    }\n\n    Log::logger->log(Log::WARN, \"No rules configured. Using the default hardcoded one.\");\n\n    return applyWlrOutputConfig(SMonitorRule{.autoDir    = eAutoDirs::DIR_AUTO_RIGHT,\n                                             .name       = \"\",\n                                             .resolution = Vector2D(0, 0),\n                                             .offset     = Vector2D(-INT32_MAX, -INT32_MAX),\n                                             .scale      = -1}); // 0, 0 is preferred and -1, -1 is auto\n}\n\nSWorkspaceRule CConfigManager::getWorkspaceRuleFor(PHLWORKSPACE pWorkspace) {\n    SWorkspaceRule mergedRule{};\n    for (auto const& rule : m_workspaceRules) {\n        if (!pWorkspace->matchesStaticSelector(rule.workspaceString))\n            continue;\n\n        mergedRule = mergeWorkspaceRules(mergedRule, rule);\n    }\n\n    return mergedRule;\n}\n\nSWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1, const SWorkspaceRule& rule2) {\n    SWorkspaceRule mergedRule = rule1;\n\n    if (rule1.monitor.empty())\n        mergedRule.monitor = rule2.monitor;\n    if (rule1.workspaceString.empty())\n        mergedRule.workspaceString = rule2.workspaceString;\n    if (rule1.workspaceName.empty())\n        mergedRule.workspaceName = rule2.workspaceName;\n    if (rule1.workspaceId == WORKSPACE_INVALID)\n        mergedRule.workspaceId = rule2.workspaceId;\n\n    if (rule2.isDefault)\n        mergedRule.isDefault = true;\n    if (rule2.isPersistent)\n        mergedRule.isPersistent = true;\n    if (rule2.gapsIn.has_value())\n        mergedRule.gapsIn = rule2.gapsIn;\n    if (rule2.gapsOut.has_value())\n        mergedRule.gapsOut = rule2.gapsOut;\n    if (rule2.floatGaps)\n        mergedRule.floatGaps = rule2.floatGaps;\n    if (rule2.borderSize.has_value())\n        mergedRule.borderSize = rule2.borderSize;\n    if (rule2.noBorder.has_value())\n        mergedRule.noBorder = rule2.noBorder;\n    if (rule2.noRounding.has_value())\n        mergedRule.noRounding = rule2.noRounding;\n    if (rule2.decorate.has_value())\n        mergedRule.decorate = rule2.decorate;\n    if (rule2.noShadow.has_value())\n        mergedRule.noShadow = rule2.noShadow;\n    if (rule2.onCreatedEmptyRunCmd.has_value())\n        mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd;\n    if (rule2.defaultName.has_value())\n        mergedRule.defaultName = rule2.defaultName;\n    if (rule2.layout.has_value())\n        mergedRule.layout = rule2.layout;\n    if (!rule2.layoutopts.empty()) {\n        for (const auto& layoutopt : rule2.layoutopts) {\n            mergedRule.layoutopts[layoutopt.first] = layoutopt.second;\n        }\n    }\n    if (rule2.animationStyle.has_value())\n        mergedRule.animationStyle = rule2.animationStyle;\n    return mergedRule;\n}\n\nvoid CConfigManager::dispatchExecOnce() {\n    if (m_firstExecDispatched || m_isFirstLaunch)\n        return;\n\n    // update dbus env\n    if (g_pCompositor->m_aqBackend->hasSession())\n        handleRawExec(\"\",\n#ifdef USES_SYSTEMD\n                      \"systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash \"\n                      \"dbus-update-activation-environment 2>/dev/null && \"\n#endif\n                      \"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS\");\n\n    m_firstExecDispatched = true;\n    m_isLaunchingExecOnce = true;\n\n    for (auto const& c : m_firstExecRequests) {\n        c.withRules ? handleExec(\"\", c.exec) : handleRawExec(\"\", c.exec);\n    }\n\n    m_firstExecRequests.clear(); // free some kb of memory :P\n    m_isLaunchingExecOnce = false;\n\n    // set input, fixes some certain issues\n    g_pInputManager->setKeyboardLayout();\n    g_pInputManager->setPointerConfigs();\n    g_pInputManager->setTouchDeviceConfigs();\n    g_pInputManager->setTabletConfigs();\n\n    // check for user's possible errors with their setup and notify them if needed\n    // this is additionally guarded because exiting safe mode will re-run this.\n    static bool once = true;\n    if (once) {\n        g_pCompositor->performUserChecks();\n        once = false;\n    }\n}\n\nvoid CConfigManager::dispatchExecShutdown() {\n    if (m_finalExecRequests.empty()) {\n        g_pCompositor->m_finalRequests = false;\n        return;\n    }\n\n    g_pCompositor->m_finalRequests = true;\n\n    for (auto const& c : m_finalExecRequests) {\n        handleExecShutdown(\"\", c);\n    }\n\n    m_finalExecRequests.clear();\n\n    // Actually exit now\n    handleExecShutdown(\"\", \"hyprctl dispatch exit\");\n}\n\nvoid CConfigManager::performMonitorReload() {\n    handleMonitorv2();\n\n    bool overAgain = false;\n\n    for (auto const& m : g_pCompositor->m_realMonitors) {\n        if (!m->m_output || m->m_isUnsafeFallback)\n            continue;\n\n        auto rule = getMonitorRuleFor(m);\n\n        if (!m->applyMonitorRule(&rule)) {\n            overAgain = true;\n            break;\n        }\n\n        // ensure mirror\n        m->setMirror(rule.mirrorOf);\n\n        g_pHyprRenderer->arrangeLayersForMonitor(m->m_id);\n    }\n\n    if (overAgain)\n        performMonitorReload();\n\n    m_wantsMonitorReload = false;\n\n    Event::bus()->m_events.monitor.layoutChanged.emit();\n}\n\nvoid* const* CConfigManager::getConfigValuePtr(const std::string& val) {\n    const auto VAL = m_config->getConfigValuePtr(val.c_str());\n    if (!VAL)\n        return nullptr;\n    return VAL->getDataStaticPtr();\n}\n\nHyprlang::CConfigValue* CConfigManager::getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat) {\n    if (!specialCat.empty())\n        return m_config->getSpecialConfigValuePtr(specialCat.c_str(), name.c_str(), nullptr);\n\n    if (name.starts_with(\"plugin:\"))\n        return m_config->getSpecialConfigValuePtr(\"plugin\", name.substr(7).c_str(), nullptr);\n\n    return m_config->getConfigValuePtr(name.c_str());\n}\n\nbool CConfigManager::deviceConfigExists(const std::string& dev) {\n    auto copy = dev;\n    std::ranges::replace(copy, ' ', '-');\n\n    return m_config->specialCategoryExistsForKey(\"device\", copy.c_str());\n}\n\nvoid CConfigManager::ensureMonitorStatus() {\n    for (auto const& rm : g_pCompositor->m_realMonitors) {\n        if (!rm->m_output || rm->m_isUnsafeFallback)\n            continue;\n\n        auto rule = getMonitorRuleFor(rm);\n\n        if (rule.disabled == rm->m_enabled)\n            rm->applyMonitorRule(&rule);\n    }\n}\n\nvoid CConfigManager::ensureVRR(PHLMONITOR pMonitor) {\n    static auto PVRR = rc<Hyprlang::INT* const*>(getConfigValuePtr(\"misc:vrr\"));\n\n    static auto ensureVRRForDisplay = [&](PHLMONITOR m) -> void {\n        if (!m->m_output || m->m_createdByUser)\n            return;\n\n        const auto USEVRR = m->m_activeMonitorRule.vrr.has_value() ? m->m_activeMonitorRule.vrr.value() : **PVRR;\n\n        if (USEVRR == 0) {\n            if (m->m_vrrActive) {\n                m->m_output->state->resetExplicitFences();\n                m->m_output->state->setAdaptiveSync(false);\n\n                if (!m->m_state.commit())\n                    Log::logger->log(Log::ERR, \"Couldn't commit output {} in ensureVRR -> false\", m->m_output->name);\n            }\n            m->m_vrrActive = false;\n            return;\n        }\n\n        const auto PWORKSPACE = m->m_activeWorkspace;\n\n        if (USEVRR == 1) {\n            bool wantVRR = true;\n            if (PWORKSPACE && PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN))\n                wantVRR = !PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault();\n\n            if (wantVRR) {\n                if (!m->m_vrrActive) {\n                    m->m_output->state->resetExplicitFences();\n                    m->m_output->state->setAdaptiveSync(true);\n\n                    if (!m->m_state.test()) {\n                        Log::logger->log(Log::DEBUG, \"Pending output {} does not accept VRR.\", m->m_output->name);\n                        m->m_output->state->setAdaptiveSync(false);\n                    }\n\n                    if (!m->m_state.commit())\n                        Log::logger->log(Log::ERR, \"Couldn't commit output {} in ensureVRR -> true\", m->m_output->name);\n                }\n                m->m_vrrActive = true;\n            } else {\n                if (m->m_vrrActive) {\n                    m->m_output->state->resetExplicitFences();\n                    m->m_output->state->setAdaptiveSync(false);\n\n                    if (!m->m_state.commit())\n                        Log::logger->log(Log::ERR, \"Couldn't commit output {} in ensureVRR -> false\", m->m_output->name);\n                }\n                m->m_vrrActive = false;\n            }\n            return;\n        } else if (USEVRR == 2 || USEVRR == 3) {\n            if (!PWORKSPACE)\n                return; // ???\n\n            bool wantVRR = PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN);\n            if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault())\n                wantVRR = false;\n\n            if (wantVRR && USEVRR == 3) {\n                const auto contentType = PWORKSPACE->getFullscreenWindow()->getContentType();\n                wantVRR                = contentType == CONTENT_TYPE_GAME || contentType == CONTENT_TYPE_VIDEO;\n            }\n\n            if (wantVRR) {\n                /* fullscreen */\n                m->m_vrrActive = true;\n\n                if (!m->m_output->state->state().adaptiveSync) {\n                    m->m_output->state->setAdaptiveSync(true);\n\n                    if (!m->m_state.test()) {\n                        Log::logger->log(Log::DEBUG, \"Pending output {} does not accept VRR.\", m->m_output->name);\n                        m->m_output->state->setAdaptiveSync(false);\n                    }\n                }\n            } else {\n                m->m_vrrActive = false;\n\n                m->m_output->state->setAdaptiveSync(false);\n            }\n        }\n    };\n\n    if (pMonitor) {\n        ensureVRRForDisplay(pMonitor);\n        return;\n    }\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        ensureVRRForDisplay(m);\n    }\n}\n\nSP<SAnimationPropertyConfig> CConfigManager::getAnimationPropertyConfig(const std::string& name) {\n    return m_animationTree.getConfig(name);\n}\n\nvoid CConfigManager::addParseError(const std::string& err) {\n    g_pHyprError->queueCreate(err + \"\\nHyprland may not work correctly.\", CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0));\n}\n\nPHLMONITOR CConfigManager::getBoundMonitorForWS(const std::string& wsname) {\n    auto monitor = getBoundMonitorStringForWS(wsname);\n    if (monitor.starts_with(\"desc:\"))\n        return g_pCompositor->getMonitorFromDesc(trim(monitor.substr(5)));\n    else\n        return g_pCompositor->getMonitorFromName(monitor);\n}\n\nstd::string CConfigManager::getBoundMonitorStringForWS(const std::string& wsname) {\n    for (auto const& wr : m_workspaceRules) {\n        const auto WSNAME = wr.workspaceName.starts_with(\"name:\") ? wr.workspaceName.substr(5) : wr.workspaceName;\n        if (WSNAME == wsname)\n            return wr.monitor;\n    }\n\n    return \"\";\n}\n\nconst std::vector<SWorkspaceRule>& CConfigManager::getAllWorkspaceRules() {\n    return m_workspaceRules;\n}\n\nvoid CConfigManager::handlePluginLoads() {\n    if (!g_pPluginSystem)\n        return;\n\n    bool pluginsChanged = false;\n    g_pPluginSystem->updateConfigPlugins(m_declaredPlugins, pluginsChanged);\n\n    if (pluginsChanged) {\n        g_pHyprError->destroy();\n        reload();\n    }\n}\n\nconst std::unordered_map<std::string, SP<SAnimationPropertyConfig>>& CConfigManager::getAnimationConfig() {\n    return m_animationTree.getFullConfig();\n}\n\nvoid CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value) {\n    if (!name.starts_with(\"plugin:\"))\n        return;\n\n    std::string field = name.substr(7);\n\n    m_config->addSpecialConfigValue(\"plugin\", field.c_str(), value);\n    m_pluginVariables.push_back({handle, field});\n}\n\nvoid CConfigManager::addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fn, Hyprlang::SHandlerOptions opts) {\n    m_pluginKeywords.emplace_back(SPluginKeyword{handle, name, fn});\n    m_config->registerHandler(fn, name.c_str(), opts);\n}\n\nvoid CConfigManager::removePluginConfig(HANDLE handle) {\n    for (auto const& k : m_pluginKeywords) {\n        if (k.handle != handle)\n            continue;\n\n        m_config->unregisterHandler(k.name.c_str());\n    }\n\n    std::erase_if(m_pluginKeywords, [&](const auto& other) { return other.handle == handle; });\n    for (auto const& [h, n] : m_pluginVariables) {\n        if (h != handle)\n            continue;\n\n        m_config->removeSpecialConfigValue(\"plugin\", n.c_str());\n    }\n    std::erase_if(m_pluginVariables, [handle](const auto& other) { return other.handle == handle; });\n}\n\nstd::string CConfigManager::getDefaultWorkspaceFor(const std::string& name) {\n    for (auto other = m_workspaceRules.begin(); other != m_workspaceRules.end(); ++other) {\n        if (other->isDefault) {\n            if (other->monitor == name)\n                return other->workspaceString;\n            if (other->monitor.starts_with(\"desc:\")) {\n                auto const monitor = g_pCompositor->getMonitorFromDesc(trim(other->monitor.substr(5)));\n                if (monitor && monitor->m_name == name)\n                    return other->workspaceString;\n            }\n        }\n    }\n    return \"\";\n}\n\nstd::optional<std::string> CConfigManager::handleRawExec(const std::string& command, const std::string& args) {\n    if (m_isFirstLaunch) {\n        m_firstExecRequests.push_back({args, false});\n        return {};\n    }\n\n    g_pKeybindManager->spawnRaw(args);\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleExec(const std::string& command, const std::string& args) {\n    if (m_isFirstLaunch) {\n        m_firstExecRequests.push_back({args, true});\n        return {};\n    }\n\n    g_pKeybindManager->spawn(args);\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleExecOnce(const std::string& command, const std::string& args) {\n    if (m_isFirstLaunch)\n        m_firstExecRequests.push_back({args, true});\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleExecRawOnce(const std::string& command, const std::string& args) {\n    if (m_isFirstLaunch)\n        m_firstExecRequests.push_back({args, false});\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleExecShutdown(const std::string& command, const std::string& args) {\n    if (g_pCompositor->m_finalRequests) {\n        g_pKeybindManager->spawn(args);\n        return {};\n    }\n\n    m_finalExecRequests.push_back(args);\n    return {};\n}\n\nstatic bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) {\n    auto args = CVarList(modeline, 0, 's');\n\n    auto keyword = args[0];\n    std::ranges::transform(keyword, keyword.begin(), ::tolower);\n\n    if (keyword != \"modeline\")\n        return false;\n\n    if (args.size() < 10) {\n        Log::logger->log(Log::ERR, \"modeline parse error: expected at least 9 arguments, got {}\", args.size() - 1);\n        return false;\n    }\n\n    int argno = 1;\n\n    try {\n        mode.type        = DRM_MODE_TYPE_USERDEF;\n        mode.clock       = std::stof(args[argno++]) * 1000;\n        mode.hdisplay    = std::stoi(args[argno++]);\n        mode.hsync_start = std::stoi(args[argno++]);\n        mode.hsync_end   = std::stoi(args[argno++]);\n        mode.htotal      = std::stoi(args[argno++]);\n        mode.vdisplay    = std::stoi(args[argno++]);\n        mode.vsync_start = std::stoi(args[argno++]);\n        mode.vsync_end   = std::stoi(args[argno++]);\n        mode.vtotal      = std::stoi(args[argno++]);\n        mode.vrefresh    = mode.clock * 1000.0 * 1000.0 / mode.htotal / mode.vtotal;\n    } catch (const std::exception& e) {\n        Log::logger->log(Log::ERR, \"modeline parse error: invalid numeric value: {}\", e.what());\n        return false;\n    }\n\n    // clang-format off\n    static std::unordered_map<std::string, uint32_t> flagsmap = {\n        {\"+hsync\", DRM_MODE_FLAG_PHSYNC},\n        {\"-hsync\", DRM_MODE_FLAG_NHSYNC},\n        {\"+vsync\", DRM_MODE_FLAG_PVSYNC},\n        {\"-vsync\", DRM_MODE_FLAG_NVSYNC},\n        {\"Interlace\", DRM_MODE_FLAG_INTERLACE},\n    };\n    // clang-format on\n\n    for (; argno < sc<int>(args.size()); argno++) {\n        auto key = args[argno];\n        std::ranges::transform(key, key.begin(), ::tolower);\n\n        auto it = flagsmap.find(key);\n\n        if (it != flagsmap.end())\n            mode.flags |= it->second;\n        else\n            Log::logger->log(Log::ERR, \"Invalid flag {} in modeline\", key);\n    }\n\n    snprintf(mode.name, sizeof(mode.name), \"%dx%d@%d\", mode.hdisplay, mode.vdisplay, mode.vrefresh / 1000);\n\n    return true;\n}\n\nCMonitorRuleParser::CMonitorRuleParser(const std::string& name) {\n    m_rule.name = name;\n}\n\nconst std::string& CMonitorRuleParser::name() {\n    return m_rule.name;\n}\n\nSMonitorRule& CMonitorRuleParser::rule() {\n    return m_rule;\n}\n\nstd::optional<std::string> CMonitorRuleParser::getError() {\n    if (m_error.empty())\n        return {};\n    return m_error;\n}\n\nbool CMonitorRuleParser::parseMode(const std::string& value) {\n    if (value.starts_with(\"pref\"))\n        m_rule.resolution = Vector2D();\n    else if (value.starts_with(\"highrr\"))\n        m_rule.resolution = Vector2D(-1, -1);\n    else if (value.starts_with(\"highres\"))\n        m_rule.resolution = Vector2D(-1, -2);\n    else if (value.starts_with(\"maxwidth\"))\n        m_rule.resolution = Vector2D(-1, -3);\n    else if (parseModeLine(value, m_rule.drmMode)) {\n        m_rule.resolution  = Vector2D(m_rule.drmMode.hdisplay, m_rule.drmMode.vdisplay);\n        m_rule.refreshRate = sc<float>(m_rule.drmMode.vrefresh) / 1000;\n    } else {\n\n        if (!value.contains(\"x\")) {\n            m_error += \"invalid resolution \";\n            m_rule.resolution = Vector2D();\n            return false;\n        } else {\n            try {\n                m_rule.resolution.x = stoi(value.substr(0, value.find_first_of('x')));\n                m_rule.resolution.y = stoi(value.substr(value.find_first_of('x') + 1, value.find_first_of('@')));\n\n                if (value.contains(\"@\"))\n                    m_rule.refreshRate = stof(value.substr(value.find_first_of('@') + 1));\n            } catch (...) {\n                m_error += \"invalid resolution \";\n                m_rule.resolution = Vector2D();\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nbool CMonitorRuleParser::parsePosition(const std::string& value, bool isFirst) {\n    if (value.starts_with(\"auto\")) {\n        m_rule.offset = Vector2D(-INT32_MAX, -INT32_MAX);\n        // If this is the first monitor rule needs to be on the right.\n        if (value == \"auto-right\" || value == \"auto\" || isFirst)\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_RIGHT;\n        else if (value == \"auto-left\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_LEFT;\n        else if (value == \"auto-up\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_UP;\n        else if (value == \"auto-down\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_DOWN;\n        else if (value == \"auto-center-right\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_RIGHT;\n        else if (value == \"auto-center-left\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_LEFT;\n        else if (value == \"auto-center-up\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_UP;\n        else if (value == \"auto-center-down\")\n            m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_DOWN;\n        else {\n            Log::logger->log(Log::WARN,\n                             \"Invalid auto direction. Valid options are 'auto',\"\n                             \"'auto-up', 'auto-down', 'auto-left', 'auto-right',\"\n                             \"'auto-center-up', 'auto-center-down',\"\n                             \"'auto-center-left', and 'auto-center-right'.\");\n            m_error += \"invalid auto direction \";\n            return false;\n        }\n    } else {\n        if (!value.contains(\"x\")) {\n            m_error += \"invalid offset \";\n            m_rule.offset = Vector2D(-INT32_MAX, -INT32_MAX);\n            return false;\n        } else {\n            try {\n                m_rule.offset.x = stoi(value.substr(0, value.find_first_of('x')));\n                m_rule.offset.y = stoi(value.substr(value.find_first_of('x') + 1));\n            } catch (...) {\n                m_error += \"invalid offset \";\n                m_rule.offset = Vector2D(-INT32_MAX, -INT32_MAX);\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nbool CMonitorRuleParser::parseScale(const std::string& value) {\n    if (value.starts_with(\"auto\"))\n        m_rule.scale = -1;\n    else {\n        if (!isNumber(value, true)) {\n            m_error += \"invalid scale \";\n            return false;\n        } else {\n            m_rule.scale = stof(value);\n\n            if (m_rule.scale < 0.25f) {\n                m_error += \"invalid scale \";\n                m_rule.scale = 1;\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nbool CMonitorRuleParser::parseTransform(const std::string& value) {\n    if (!isNumber(value)) {\n        m_error += \"invalid transform \";\n        return false;\n    }\n\n    const auto TSF = std::stoi(value);\n    if (std::clamp(TSF, 0, 7) != TSF) {\n        Log::logger->log(Log::ERR, \"Invalid transform {} in monitor\", TSF);\n        m_error += \"invalid transform \";\n        return false;\n    }\n    m_rule.transform = sc<wl_output_transform>(TSF);\n    return true;\n}\n\nbool CMonitorRuleParser::parseBitdepth(const std::string& value) {\n    m_rule.enable10bit = value == \"10\";\n    return true;\n}\n\nbool CMonitorRuleParser::parseCM(const std::string& value) {\n    auto parsedCM = NCMType::fromString(value);\n    if (!parsedCM.has_value()) {\n        m_error += \"invalid cm \";\n        return false;\n    }\n    m_rule.cmType = parsedCM.value();\n    return true;\n}\n\nbool CMonitorRuleParser::parseSDRBrightness(const std::string& value) {\n    try {\n        m_rule.sdrBrightness = stof(value);\n    } catch (...) {\n        m_error += \"invalid sdrbrightness \";\n        return false;\n    }\n    return true;\n}\n\nbool CMonitorRuleParser::parseSDRSaturation(const std::string& value) {\n    try {\n        m_rule.sdrSaturation = stof(value);\n    } catch (...) {\n        m_error += \"invalid sdrsaturation \";\n        return false;\n    }\n    return true;\n}\n\nbool CMonitorRuleParser::parseVRR(const std::string& value) {\n    if (!isNumber(value)) {\n        m_error += \"invalid vrr \";\n        return false;\n    }\n\n    m_rule.vrr = std::stoi(value);\n    return true;\n}\n\nbool CMonitorRuleParser::parseICC(const std::string& val) {\n    if (val.empty()) {\n        m_error += \"invalid icc \";\n        return false;\n    }\n    m_rule.iccFile = val;\n    return true;\n}\n\nvoid CMonitorRuleParser::setDisabled() {\n    m_rule.disabled = true;\n}\n\nvoid CMonitorRuleParser::setMirror(const std::string& value) {\n    m_rule.mirrorOf = value;\n}\n\nbool CMonitorRuleParser::setReserved(const Desktop::CReservedArea& value) {\n    m_rule.reservedArea = value;\n    return true;\n}\n\nstd::optional<std::string> CConfigManager::handleMonitor(const std::string& command, const std::string& args) {\n    // get the monitor config\n    const auto ARGS = CVarList2(std::string(args));\n\n    auto       parser = CMonitorRuleParser(std::string(ARGS[0]));\n\n    if (ARGS[1] == \"disable\" || ARGS[1] == \"disabled\" || ARGS[1] == \"addreserved\" || ARGS[1] == \"transform\") {\n        if (ARGS[1] == \"disable\" || ARGS[1] == \"disabled\")\n            parser.setDisabled();\n        else if (ARGS[1] == \"transform\") {\n            if (!parser.parseTransform(std::string(ARGS[2])))\n                return parser.getError();\n\n            const auto TRANSFORM = parser.rule().transform;\n\n            // overwrite if exists\n            for (auto& r : m_monitorRules) {\n                if (r.name == parser.name()) {\n                    r.transform = TRANSFORM;\n                    return {};\n                }\n            }\n\n            return {};\n        } else if (ARGS[1] == \"addreserved\") {\n            std::optional<Desktop::CReservedArea> area;\n            try {\n                // top, right, bottom, left\n                area = {std::stoi(std::string{ARGS[2]}), std::stoi(std::string{ARGS[5]}), std::stoi(std::string{ARGS[3]}), std::stoi(std::string{ARGS[4]})};\n            } catch (...) { return \"parse error: invalid reserved area\"; }\n\n            if (!area.has_value())\n                return \"parse error: bad addreserved\";\n\n            auto rule = std::ranges::find_if(m_monitorRules, [n = ARGS[0]](const auto& other) { return other.name == n; });\n            if (rule != m_monitorRules.end()) {\n                rule->reservedArea = area.value();\n                return {};\n            }\n\n            // fall\n        } else {\n            Log::logger->log(Log::ERR, \"ConfigManager parseMonitor, curitem bogus???\");\n            return \"parse error: curitem bogus\";\n        }\n\n        std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == parser.name(); });\n\n        m_monitorRules.push_back(parser.rule());\n\n        return {};\n    }\n\n    parser.parseMode(std::string(ARGS[1]));\n    parser.parsePosition(std::string(ARGS[2]));\n    parser.parseScale(std::string(ARGS[3]));\n\n    int argno = 4;\n\n    while (!ARGS[argno].empty()) {\n        if (ARGS[argno] == \"mirror\") {\n            parser.setMirror(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"bitdepth\") {\n            parser.parseBitdepth(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"cm\") {\n            parser.parseCM(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"sdrsaturation\") {\n            parser.parseSDRSaturation(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"sdrbrightness\") {\n            parser.parseSDRBrightness(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"transform\") {\n            parser.parseTransform(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"vrr\") {\n            parser.parseVRR(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"icc\") {\n            parser.parseICC(std::string(ARGS[argno + 1]));\n            argno++;\n        } else if (ARGS[argno] == \"workspace\") {\n            const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1]));\n\n            SWorkspaceRule wsRule;\n            wsRule.monitor         = parser.name();\n            wsRule.workspaceString = ARGS[argno + 1];\n            wsRule.workspaceId     = isAutoID ? WORKSPACE_INVALID : id;\n            wsRule.workspaceName   = name;\n\n            m_workspaceRules.emplace_back(wsRule);\n            argno++;\n        } else {\n            Log::logger->log(Log::ERR, \"Config error: invalid monitor syntax at \\\"{}\\\"\", ARGS[argno]);\n            return \"invalid syntax at \\\"\" + std::string(ARGS[argno]) + \"\\\"\";\n        }\n\n        argno++;\n    }\n\n    auto newrule = parser.rule();\n\n    std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; });\n\n    m_monitorRules.push_back(newrule);\n\n    return parser.getError();\n}\n\nstd::optional<std::string> CConfigManager::handleBezier(const std::string& command, const std::string& args) {\n    const auto  ARGS = CVarList(args);\n\n    std::string bezierName = ARGS[0];\n\n    if (ARGS[1].empty())\n        return \"too few arguments\";\n    else if (!isNumber(ARGS[1], true))\n        return \"invalid point\";\n    float p1x = std::stof(ARGS[1]);\n\n    if (ARGS[2].empty())\n        return \"too few arguments\";\n    else if (!isNumber(ARGS[2], true))\n        return \"invalid point\";\n    float p1y = std::stof(ARGS[2]);\n\n    if (ARGS[3].empty())\n        return \"too few arguments\";\n    else if (!isNumber(ARGS[3], true))\n        return \"invalid point\";\n    float p2x = std::stof(ARGS[3]);\n\n    if (ARGS[4].empty())\n        return \"too few arguments\";\n    else if (!isNumber(ARGS[4], true))\n        return \"invalid point\";\n    float p2y = std::stof(ARGS[4]);\n\n    if (!ARGS[5].empty())\n        return \"too many arguments\";\n\n    g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y));\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleAnimation(const std::string& command, const std::string& args) {\n    const auto ARGS = CVarList(args);\n\n    // Master on/off\n\n    // anim name\n    const auto ANIMNAME = ARGS[0];\n\n    if (!m_animationTree.nodeExists(ANIMNAME))\n        return \"no such animation\";\n\n    // This helper casts strings like \"1\", \"true\", \"off\", \"yes\"... to int.\n    int64_t enabledInt = configStringToInt(ARGS[1]).value_or(0) == 1;\n\n    // Checking that the int is 1 or 0 because the helper can return integers out of range.\n    if (enabledInt != 0 && enabledInt != 1)\n        return \"invalid animation on/off state\";\n\n    if (!enabledInt) {\n        m_animationTree.setConfigForNode(ANIMNAME, enabledInt, 1, \"default\");\n        return {};\n    }\n\n    float speed = -1;\n\n    // speed\n    if (isNumber(ARGS[2], true)) {\n        speed = std::stof(ARGS[2]);\n\n        if (speed <= 0) {\n            speed = 1.f;\n            return \"invalid speed\";\n        }\n    } else {\n        speed = 10.f;\n        return \"invalid speed\";\n    }\n\n    std::string bezierName = ARGS[3];\n    m_animationTree.setConfigForNode(ANIMNAME, enabledInt, speed, ARGS[3], ARGS[4]);\n\n    if (!g_pAnimationManager->bezierExists(bezierName)) {\n        const auto PANIMNODE      = m_animationTree.getConfig(ANIMNAME);\n        PANIMNODE->internalBezier = \"default\";\n        return \"no such bezier\";\n    }\n\n    if (!ARGS[4].empty()) {\n        auto ERR = g_pAnimationManager->styleValidInConfigVar(ANIMNAME, ARGS[4]);\n\n        if (!ERR.empty())\n            return ERR;\n    }\n\n    return {};\n}\n\nSParsedKey parseKey(const std::string& key) {\n    if (isNumber(key) && std::stoi(key) > 9)\n        return {.keycode = std::stoi(key)};\n    else if (key.starts_with(\"code:\") && isNumber(key.substr(5)))\n        return {.keycode = std::stoi(key.substr(5))};\n    else if (key == \"catchall\")\n        return {.catchAll = true};\n    else\n        return {.key = key};\n}\n\nstd::optional<std::string> CConfigManager::handleBind(const std::string& command, const std::string& value) {\n    // example:\n    // bind[fl]=SUPER,G,exec,dmenu_run <args>\n\n    // flags\n    bool       locked          = false;\n    bool       release         = false;\n    bool       repeat          = false;\n    bool       mouse           = false;\n    bool       nonConsuming    = false;\n    bool       transparent     = false;\n    bool       ignoreMods      = false;\n    bool       multiKey        = false;\n    bool       longPress       = false;\n    bool       hasDescription  = false;\n    bool       dontInhibit     = false;\n    bool       click           = false;\n    bool       drag            = false;\n    bool       submapUniversal = false;\n    bool       isPerDevice     = false;\n    const auto BINDARGS        = command.substr(4);\n\n    for (auto const& arg : BINDARGS) {\n        switch (arg) {\n            case 'l': locked = true; break;\n            case 'r': release = true; break;\n            case 'e': repeat = true; break;\n            case 'm': mouse = true; break;\n            case 'n': nonConsuming = true; break;\n            case 't': transparent = true; break;\n            case 'i': ignoreMods = true; break;\n            case 's': multiKey = true; break;\n            case 'o': longPress = true; break;\n            case 'd': hasDescription = true; break;\n            case 'p': dontInhibit = true; break;\n            case 'c':\n                click   = true;\n                release = true;\n                break;\n            case 'g':\n                drag    = true;\n                release = true;\n                break;\n            case 'u': submapUniversal = true; break;\n            case 'k': isPerDevice = true; break;\n            default: return \"bind: invalid flag\";\n        }\n    }\n\n    if ((longPress || release) && repeat)\n        return \"flags e is mutually exclusive with r and o\";\n\n    if (mouse && (repeat || release || locked))\n        return \"flag m is exclusive\";\n\n    if (click && drag)\n        return \"flags c and g are mutually exclusive\";\n\n    const int  numbArgs = (hasDescription ? 5 : 4) + sc<int>(isPerDevice);\n    const auto ARGS     = CVarList(value, numbArgs);\n\n    const int  DESCR_OFFSET  = hasDescription ? 1 : 0;\n    const int  DEVICE_OFFSET = sc<int>(isPerDevice);\n    if ((ARGS.size() < 3 && !mouse) || (ARGS.size() < 3 && mouse))\n        return \"bind: too few args\";\n    else if ((ARGS.size() > sc<size_t>(4) + DESCR_OFFSET + DEVICE_OFFSET && !mouse) || (ARGS.size() > sc<size_t>(3) + DESCR_OFFSET + DEVICE_OFFSET && mouse))\n        return \"bind: too many args\";\n\n    std::set<xkb_keysym_t> KEYSYMS;\n    std::set<xkb_keysym_t> MODS;\n\n    if (multiKey) {\n        for (const auto& splitKey : CVarList(ARGS[1], 8, '&')) {\n            KEYSYMS.insert(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));\n        }\n        for (const auto& splitMod : CVarList(ARGS[0], 8, '&')) {\n            MODS.insert(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));\n        }\n    }\n    const auto MOD    = g_pKeybindManager->stringToModMask(ARGS[0]);\n    const auto MODSTR = ARGS[0];\n\n    const auto KEY = multiKey ? \"\" : ARGS[1];\n\n    const auto DEVICEARGS = isPerDevice ? ARGS[2] : \"\";\n\n    const auto DESCRIPTION = hasDescription ? ARGS[2 + DEVICE_OFFSET] : \"\";\n\n    auto       HANDLER = ARGS[2 + DESCR_OFFSET + DEVICE_OFFSET];\n\n    const auto COMMAND = mouse ? HANDLER : ARGS[3 + DESCR_OFFSET + DEVICE_OFFSET];\n\n    if (mouse)\n        HANDLER = \"mouse\";\n\n    // to lower\n    std::ranges::transform(HANDLER, HANDLER.begin(), ::tolower);\n\n    const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(HANDLER);\n\n    if (DISPATCHER == g_pKeybindManager->m_dispatchers.end()) {\n        Log::logger->log(Log::ERR, \"Invalid dispatcher: {}\", HANDLER);\n        return \"Invalid dispatcher, requested \\\"\" + HANDLER + \"\\\" does not exist\";\n    }\n\n    if (MOD == 0 && !MODSTR.empty()) {\n        Log::logger->log(Log::ERR, \"Invalid mod: {}\", MODSTR);\n        return \"Invalid mod, requested mod \\\"\" + MODSTR + \"\\\" is not a valid mod.\";\n    }\n\n    //[!]keyboard1 keyboard2 ...\n    bool                            deviceInclusive = false;\n    std::unordered_set<std::string> devices         = {};\n    if (!DEVICEARGS.empty()) {\n        deviceInclusive = DEVICEARGS[0] != '!';\n        for (const auto deviceString : std::ranges::views::split(DEVICEARGS.substr(deviceInclusive ? 0 : 1), ' ')) {\n            devices.emplace(std::string_view(deviceString));\n        }\n    }\n\n    if ((!KEY.empty()) || multiKey) {\n        SParsedKey parsedKey = parseKey(KEY);\n\n        if (parsedKey.catchAll && m_currentSubmap.name.empty()) {\n            Log::logger->log(Log::ERR, \"Catchall not allowed outside of submap!\");\n            return \"Invalid catchall, catchall keybinds are only allowed in submaps.\";\n        }\n\n        g_pKeybindManager->addKeybind(SKeybind{parsedKey.key, KEYSYMS,      parsedKey.keycode, parsedKey.catchAll, MOD,      MODS,           HANDLER,\n                                               COMMAND,       locked,       m_currentSubmap,   DESCRIPTION,        release,  repeat,         longPress,\n                                               mouse,         nonConsuming, transparent,       ignoreMods,         multiKey, hasDescription, dontInhibit,\n                                               click,         drag,         submapUniversal,   deviceInclusive,    devices});\n    }\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleUnbind(const std::string& command, const std::string& value) {\n    const auto ARGS = CVarList(value);\n\n    if (ARGS.size() == 1 && ARGS[0] == \"all\") {\n        g_pKeybindManager->m_keybinds.clear();\n        g_pKeybindManager->m_activeKeybinds.clear();\n        g_pKeybindManager->m_lastLongPressKeybind.reset();\n        return {};\n    }\n\n    const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]);\n\n    const auto KEY = parseKey(ARGS[1]);\n\n    g_pKeybindManager->removeKeybind(MOD, KEY);\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleWorkspaceRules(const std::string& command, const std::string& value) {\n    // This can either be the monitor or the workspace identifier\n    const auto FIRST_DELIM = value.find_first_of(',');\n\n    auto       first_ident = trim(value.substr(0, FIRST_DELIM));\n\n    const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(first_ident);\n\n    auto           rules = value.substr(FIRST_DELIM + 1);\n    SWorkspaceRule wsRule;\n    wsRule.workspaceString = first_ident;\n    // if (id == WORKSPACE_INVALID) {\n    //     // it could be the monitor. If so, second value MUST be\n    //     // the workspace.\n    //     const auto WORKSPACE_DELIM = value.find_first_of(',', FIRST_DELIM + 1);\n    //     auto       wsIdent         = removeBeginEndSpacesTabs(value.substr(FIRST_DELIM + 1, (WORKSPACE_DELIM - FIRST_DELIM - 1)));\n    //     id                         = getWorkspaceIDFromString(wsIdent, name);\n    //     if (id == WORKSPACE_INVALID) {\n    //         Log::logger->log(Log::ERR, \"Invalid workspace identifier found: {}\", wsIdent);\n    //         return \"Invalid workspace identifier found: \" + wsIdent;\n    //     }\n    //     wsRule.monitor         = first_ident;\n    //     wsRule.workspaceString = wsIdent;\n    //     wsRule.isDefault       = true; // backwards compat\n    //     rules                  = value.substr(WORKSPACE_DELIM + 1);\n    // }\n\n    const static std::string ruleOnCreatedEmpty    = \"on-created-empty:\";\n    const static auto        ruleOnCreatedEmptyLen = ruleOnCreatedEmpty.length();\n\n#define CHECK_OR_THROW(expr)                                                                                                                                                       \\\n                                                                                                                                                                                   \\\n    auto X = expr;                                                                                                                                                                 \\\n    if (!X) {                                                                                                                                                                      \\\n        return \"Failed parsing a workspace rule\";                                                                                                                                  \\\n    }\n\n    auto assignRule = [&](std::string rule) -> std::optional<std::string> {\n        size_t delim = std::string::npos;\n        if ((delim = rule.find(\"gapsin:\")) != std::string::npos) {\n            CVarList2 varlist(rule.substr(delim + 7), 0, ' ');\n            wsRule.gapsIn = CCssGapData();\n            try {\n                wsRule.gapsIn->parseGapData(varlist);\n            } catch (...) { return \"Error parsing workspace rule gaps: {}\", rule.substr(delim + 7); }\n        } else if ((delim = rule.find(\"gapsout:\")) != std::string::npos) {\n            CVarList2 varlist(rule.substr(delim + 8), 0, ' ');\n            wsRule.gapsOut = CCssGapData();\n            try {\n                wsRule.gapsOut->parseGapData(varlist);\n            } catch (...) { return \"Error parsing workspace rule gaps: {}\", rule.substr(delim + 8); }\n        } else if ((delim = rule.find(\"bordersize:\")) != std::string::npos)\n            try {\n                wsRule.borderSize = std::stoi(rule.substr(delim + 11));\n            } catch (...) { return \"Error parsing workspace rule bordersize: {}\", rule.substr(delim + 11); }\n        else if ((delim = rule.find(\"border:\")) != std::string::npos) {\n            CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7)))\n            wsRule.noBorder = !*X;\n        } else if ((delim = rule.find(\"shadow:\")) != std::string::npos) {\n            CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7)))\n            wsRule.noShadow = !*X;\n        } else if ((delim = rule.find(\"rounding:\")) != std::string::npos) {\n            CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9)))\n            wsRule.noRounding = !*X;\n        } else if ((delim = rule.find(\"decorate:\")) != std::string::npos) {\n            CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9)))\n            wsRule.decorate = *X;\n        } else if ((delim = rule.find(\"monitor:\")) != std::string::npos)\n            wsRule.monitor = rule.substr(delim + 8);\n        else if ((delim = rule.find(\"default:\")) != std::string::npos) {\n            CHECK_OR_THROW(configStringToInt(rule.substr(delim + 8)))\n            wsRule.isDefault = *X;\n        } else if ((delim = rule.find(\"persistent:\")) != std::string::npos) {\n            CHECK_OR_THROW(configStringToInt(rule.substr(delim + 11)))\n            wsRule.isPersistent = *X;\n        } else if ((delim = rule.find(\"defaultName:\")) != std::string::npos)\n            wsRule.defaultName = trim(rule.substr(delim + 12));\n        else if ((delim = rule.find(ruleOnCreatedEmpty)) != std::string::npos) {\n            CHECK_OR_THROW(cleanCmdForWorkspace(name, rule.substr(delim + ruleOnCreatedEmptyLen)))\n            wsRule.onCreatedEmptyRunCmd = *X;\n        } else if ((delim = rule.find(\"layoutopt:\")) != std::string::npos) {\n            std::string opt = rule.substr(delim + 10);\n            if (!opt.contains(\":\")) {\n                // invalid\n                Log::logger->log(Log::ERR, \"Invalid workspace rule found: {}\", rule);\n                return \"Invalid workspace rule found: \" + rule;\n            }\n\n            std::string val = opt.substr(opt.find(':') + 1);\n            opt             = opt.substr(0, opt.find(':'));\n\n            wsRule.layoutopts[opt] = val;\n        } else if ((delim = rule.find(\"layout:\")) != std::string::npos) {\n            std::string layout = rule.substr(delim + 7);\n            wsRule.layout      = std::move(layout);\n        } else if ((delim = rule.find(\"animation:\")) != std::string::npos) {\n            std::string animationStyle = rule.substr(delim + 10);\n            wsRule.animationStyle      = std::move(animationStyle);\n        }\n\n        return {};\n    };\n\n#undef CHECK_OR_THROW\n\n    CVarList2 rulesList(std::string(rules), 0, ',', true);\n    for (auto const& r : rulesList) {\n        const auto R = assignRule(std::string(r));\n        if (R.has_value())\n            return R;\n    }\n\n    wsRule.workspaceName = name;\n    wsRule.workspaceId   = isAutoID ? WORKSPACE_INVALID : id;\n\n    const auto IT = std::ranges::find_if(m_workspaceRules, [&](const auto& other) { return other.workspaceString == wsRule.workspaceString; });\n\n    if (IT == m_workspaceRules.end())\n        m_workspaceRules.emplace_back(wsRule);\n    else\n        *IT = mergeWorkspaceRules(*IT, wsRule);\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleSubmap(const std::string&, const std::string& submap) {\n    CVarList2 data((std::string(submap)));\n    m_currentSubmap.name  = (data[0] == \"reset\") ? \"\" : data[0];\n    m_currentSubmap.reset = data[1];\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleSource(const std::string& command, const std::string& rawpath) {\n    if (rawpath.length() < 2) {\n        Log::logger->log(Log::ERR, \"source= path garbage\");\n        return \"source= path \" + rawpath + \" bogus!\";\n    }\n\n    std::unique_ptr<glob_t, void (*)(glob_t*)> glob_buf{sc<glob_t*>(calloc(1, sizeof(glob_t))), // allocate and zero-initialize NOLINT(cppcoreguidelines-no-malloc)\n                                                        [](glob_t* g) {\n                                                            if (g) {\n                                                                globfree(g); // free internal resources allocated by glob()\n                                                                free(g);     // free the memory for the glob_t structure NOLINT(cppcoreguidelines-no-malloc)\n                                                            }\n                                                        }};\n\n    if (auto r = glob(absolutePath(rawpath, m_configCurrentPath).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) {\n        std::string err = std::format(\"source= globbing error: {}\", r == GLOB_NOMATCH ? \"found no match\" : GLOB_ABORTED ? \"read error\" : \"out of memory\");\n        Log::logger->log(Log::ERR, \"{}\", err);\n        return err;\n    }\n\n    std::string errorsFromParsing;\n\n    for (size_t i = 0; i < glob_buf->gl_pathc; i++) {\n        auto            value = absolutePath(glob_buf->gl_pathv[i], m_configCurrentPath);\n\n        std::error_code ec;\n        auto            file_status = std::filesystem::status(value, ec);\n\n        if (ec) {\n            Log::logger->log(Log::ERR, \"source= file from glob result is inaccessible ({}): {}\", ec.message(), value);\n            return \"source= file \" + value + \" is inaccessible!\";\n        }\n\n        if (std::filesystem::is_regular_file(file_status)) {\n            m_configPaths.emplace_back(value);\n            auto configCurrentPathBackup = m_configCurrentPath;\n            m_configCurrentPath          = value;\n            const auto THISRESULT        = m_config->parseFile(value.c_str());\n            m_configCurrentPath          = configCurrentPathBackup;\n            if (THISRESULT.error && errorsFromParsing.empty())\n                errorsFromParsing += THISRESULT.getError();\n        } else if (std::filesystem::is_directory(file_status)) {\n            Log::logger->log(Log::WARN, \"source= skipping directory {}\", value);\n            continue;\n        } else {\n            Log::logger->log(Log::WARN, \"source= skipping non-regular-file {}\", value);\n            continue;\n        }\n    }\n\n    if (errorsFromParsing.empty())\n        return {};\n    return errorsFromParsing;\n}\n\nstd::optional<std::string> CConfigManager::handleEnv(const std::string& command, const std::string& value) {\n    const auto ARGS = CVarList(value, 2);\n\n    if (ARGS[0].empty())\n        return \"env empty\";\n\n    if (!m_isFirstLaunch) {\n        // check if env changed at all. If it didn't, ignore. If it did, update it.\n        const auto* ENV = getenv(ARGS[0].c_str());\n        if (ENV && ENV == ARGS[1])\n            return {}; // env hasn't changed\n    }\n\n    setenv(ARGS[0].c_str(), ARGS[1].c_str(), 1);\n\n    if (command.back() == 'd') {\n        // dbus\n        const auto CMD =\n#ifdef USES_SYSTEMD\n            \"systemctl --user import-environment \" + ARGS[0] +\n            \" && hash dbus-update-activation-environment 2>/dev/null && \"\n#endif\n            \"dbus-update-activation-environment --systemd \" +\n            ARGS[0];\n        handleRawExec(\"\", CMD);\n    }\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handlePlugin(const std::string& command, const std::string& path) {\n    if (std::ranges::find(m_declaredPlugins, path) != m_declaredPlugins.end())\n        return \"plugin '\" + path + \"' declared twice\";\n\n    m_declaredPlugins.push_back(path);\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handlePermission(const std::string& command, const std::string& value) {\n    CVarList2                   data((std::string(value)));\n\n    eDynamicPermissionType      type = PERMISSION_TYPE_UNKNOWN;\n    eDynamicPermissionAllowMode mode = PERMISSION_RULE_ALLOW_MODE_UNKNOWN;\n\n    if (data[1] == \"screencopy\")\n        type = PERMISSION_TYPE_SCREENCOPY;\n    else if (data[1] == \"cursorpos\")\n        type = PERMISSION_TYPE_CURSOR_POS;\n    else if (data[1] == \"plugin\")\n        type = PERMISSION_TYPE_PLUGIN;\n    else if (data[1] == \"keyboard\" || data[1] == \"keeb\")\n        type = PERMISSION_TYPE_KEYBOARD;\n\n    if (data[2] == \"ask\")\n        mode = PERMISSION_RULE_ALLOW_MODE_ASK;\n    else if (data[2] == \"allow\")\n        mode = PERMISSION_RULE_ALLOW_MODE_ALLOW;\n    else if (data[2] == \"deny\")\n        mode = PERMISSION_RULE_ALLOW_MODE_DENY;\n\n    if (type == PERMISSION_TYPE_UNKNOWN)\n        return \"unknown permission type\";\n    if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN)\n        return \"unknown permission allow mode\";\n\n    if (m_isFirstLaunch && g_pDynamicPermissionManager)\n        g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode);\n\n    return {};\n}\n\nstd::optional<std::string> CConfigManager::handleGesture(const std::string& command, const std::string& value) {\n    CVarList2                 data((std::string(value)));\n\n    size_t                    fingerCount = 0;\n    eTrackpadGestureDirection direction   = TRACKPAD_GESTURE_DIR_NONE;\n\n    try {\n        fingerCount = std::stoul(std::string{data[0]});\n    } catch (...) { return std::format(\"Invalid value {} for finger count\", data[0]); }\n\n    if (fingerCount <= 1 || fingerCount >= 10)\n        return std::format(\"Invalid value {} for finger count\", data[0]);\n\n    direction = g_pTrackpadGestures->dirForString(data[1]);\n\n    if (direction == TRACKPAD_GESTURE_DIR_NONE)\n        return std::format(\"Invalid direction: {}\", data[1]);\n\n    int      startDataIdx   = 2;\n    uint32_t modMask        = 0;\n    float    deltaScale     = 1.F;\n    bool     disableInhibit = false;\n\n    for (const auto arg : command.substr(7)) {\n        switch (arg) {\n            case 'p': disableInhibit = true; break;\n            default: return \"gesture: invalid flag\";\n        }\n    }\n\n    while (true) {\n\n        if (data[startDataIdx].starts_with(\"mod:\")) {\n            modMask = g_pKeybindManager->stringToModMask(std::string{data[startDataIdx].substr(4)});\n            startDataIdx++;\n            continue;\n        } else if (data[startDataIdx].starts_with(\"scale:\")) {\n            try {\n                deltaScale = std::clamp(std::stof(std::string{data[startDataIdx].substr(6)}), 0.1F, 10.F);\n                startDataIdx++;\n                continue;\n            } catch (...) { return std::format(\"Invalid delta scale: {}\", std::string{data[startDataIdx].substr(6)}); }\n        }\n\n        break;\n    }\n\n    std::expected<void, std::string> result;\n\n    if (data[startDataIdx] == \"dispatcher\")\n        result = g_pTrackpadGestures->addGesture(makeUnique<CDispatcherTrackpadGesture>(std::string{data[startDataIdx + 1]}, data.join(\",\", startDataIdx + 2)), fingerCount,\n                                                 direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"workspace\")\n        result = g_pTrackpadGestures->addGesture(makeUnique<CWorkspaceSwipeGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"resize\")\n        result = g_pTrackpadGestures->addGesture(makeUnique<CResizeTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"move\")\n        result = g_pTrackpadGestures->addGesture(makeUnique<CMoveTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"special\")\n        result =\n            g_pTrackpadGestures->addGesture(makeUnique<CSpecialWorkspaceGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"close\")\n        result = g_pTrackpadGestures->addGesture(makeUnique<CCloseTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"float\")\n        result =\n            g_pTrackpadGestures->addGesture(makeUnique<CFloatTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else if (data[startDataIdx] == \"fullscreen\")\n        result = g_pTrackpadGestures->addGesture(makeUnique<CFullscreenTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale,\n                                                 disableInhibit);\n    else if (data[startDataIdx] == \"cursorZoom\") {\n        result = g_pTrackpadGestures->addGesture(makeUnique<CCursorZoomTrackpadGesture>(std::string{data[startDataIdx + 1]}, std::string{data[startDataIdx + 2]}), fingerCount,\n                                                 direction, modMask, deltaScale, disableInhibit);\n    } else if (data[startDataIdx] == \"unset\")\n        result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit);\n    else\n        return std::format(\"Invalid gesture: {}\", data[startDataIdx]);\n\n    if (!result)\n        return result.error();\n\n    return std::nullopt;\n}\n\nstd::optional<std::string> CConfigManager::handleWindowrule(const std::string& command, const std::string& value) {\n    CVarList2                      data((std::string(value)));\n\n    SP<Desktop::Rule::CWindowRule> rule = makeShared<Desktop::Rule::CWindowRule>();\n\n    const auto&                    PROPS   = Desktop::Rule::allMatchPropStrings();\n    const auto&                    EFFECTS = Desktop::Rule::windowEffects()->allEffectStrings();\n\n    for (const auto& el : data) {\n        // split on space, no need for a CVarList here\n        size_t spacePos = el.find(' ');\n        if (spacePos == std::string::npos)\n            return std::format(\"invalid field {}: missing a value\", el);\n\n        const bool FIRST_IS_PROP = el.starts_with(\"match:\");\n        const auto FIRST         = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos);\n        if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) {\n            // it's a prop\n            const auto PROP = Desktop::Rule::matchPropFromString(FIRST);\n            if (!PROP.has_value())\n                return std::format(\"invalid prop {}\", el);\n            rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)});\n        } else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) {\n            // it's an effect\n            const auto EFFECT = Desktop::Rule::windowEffects()->get(FIRST);\n            if (!EFFECT.has_value())\n                return std::format(\"invalid effect {}\", el);\n            rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});\n        } else\n            return std::format(\"invalid field type {}\", FIRST);\n    }\n\n    m_keywordRules.emplace_back(std::move(rule));\n    if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.isDynamicKeyword)\n        Desktop::Rule::ruleEngine()->registerRule(SP<Desktop::Rule::IRule>{m_keywordRules.back()});\n\n    return std::nullopt;\n}\n\nstd::optional<std::string> CConfigManager::handleLayerrule(const std::string& command, const std::string& value) {\n    CVarList2                     data((std::string(value)));\n\n    SP<Desktop::Rule::CLayerRule> rule = makeShared<Desktop::Rule::CLayerRule>();\n\n    const auto&                   PROPS   = Desktop::Rule::allMatchPropStrings();\n    const auto&                   EFFECTS = Desktop::Rule::layerEffects()->allEffectStrings();\n\n    for (const auto& el : data) {\n        // split on space, no need for a CVarList here\n        size_t spacePos = el.find(' ');\n        if (spacePos == std::string::npos)\n            return std::format(\"invalid field {}: missing a value\", el);\n\n        const bool FIRST_IS_PROP = el.starts_with(\"match:\");\n        const auto FIRST         = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos);\n        if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) {\n            // it's a prop\n            const auto PROP = Desktop::Rule::matchPropFromString(FIRST);\n            if (!PROP.has_value())\n                return std::format(\"invalid prop {}\", el);\n            rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)});\n        } else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) {\n            // it's an effect\n            const auto EFFECT = Desktop::Rule::layerEffects()->get(FIRST);\n            if (!EFFECT.has_value())\n                return std::format(\"invalid effect {}\", el);\n            rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});\n        } else\n            return std::format(\"invalid field type {}\", FIRST);\n    }\n\n    m_keywordRules.emplace_back(std::move(rule));\n\n    return std::nullopt;\n}\n\nconst std::vector<SConfigOptionDescription>& CConfigManager::getAllDescriptions() {\n    return CONFIG_OPTIONS;\n}\n\nbool CConfigManager::shouldUseSoftwareCursors(PHLMONITOR pMonitor) {\n    static auto PNOHW      = CConfigValue<Hyprlang::INT>(\"cursor:no_hardware_cursors\");\n    static auto PINVISIBLE = CConfigValue<Hyprlang::INT>(\"cursor:invisible\");\n\n    if (pMonitor->m_tearingState.activelyTearing)\n        return true;\n\n    if (*PINVISIBLE != 0)\n        return true;\n\n    switch (*PNOHW) {\n        case 0: return false;\n        case 1: return true;\n        case 2: return g_pHyprRenderer->isNvidia() && (g_pHyprRenderer->isMgpu() || g_pCompositor->isVRRActiveOnAnyMonitor());\n        default: break;\n    }\n\n    return true;\n}\n\nstd::string SConfigOptionDescription::jsonify() const {\n    auto parseData = [this]() -> std::string {\n        return std::visit(\n            [this](auto&& val) {\n                const auto PTR = g_pConfigManager->m_config->getConfigValuePtr(value.c_str());\n                if (!PTR) {\n                    Log::logger->log(Log::ERR, \"invalid SConfigOptionDescription: no config option {} exists\", value);\n                    return std::string{\"\"};\n                }\n                const char* const EXPLICIT = PTR->m_bSetByUser ? \"true\" : \"false\";\n\n                std::string       currentValue = \"undefined\";\n\n                const auto        CONFIGVALUE = PTR->getValue();\n\n                if (typeid(Hyprlang::INT) == std::type_index(CONFIGVALUE.type()))\n                    currentValue = std::format(\"{}\", std::any_cast<Hyprlang::INT>(CONFIGVALUE));\n                else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type()))\n                    currentValue = std::format(\"{:.2f}\", std::any_cast<Hyprlang::FLOAT>(CONFIGVALUE));\n                else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type()))\n                    currentValue = std::format(\"\\\"{}\\\"\", std::any_cast<Hyprlang::STRING>(CONFIGVALUE));\n                else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) {\n                    const auto V = std::any_cast<Hyprlang::VEC2>(CONFIGVALUE);\n                    currentValue = std::format(\"\\\"{}, {}\\\"\", V.x, V.y);\n                } else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) {\n                    const auto DATA = sc<ICustomConfigValueData*>(std::any_cast<void*>(CONFIGVALUE));\n                    currentValue    = std::format(\"\\\"{}\\\"\", DATA->toString());\n                }\n\n                try {\n                    using T = std::decay_t<decltype(val)>;\n                    if constexpr (std::is_same_v<T, SStringData>) {\n                        return std::format(R\"#(     \"value\": \"{}\",\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.value, currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SRangeData>) {\n                        return std::format(R\"#(     \"value\": {},\n        \"min\": {},\n        \"max\": {},\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.value, val.min, val.max, currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SFloatData>) {\n                        return std::format(R\"#(     \"value\": {},\n        \"min\": {},\n        \"max\": {},\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.value, val.min, val.max, currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SColorData>) {\n                        return std::format(R\"#(     \"value\": \"{}\",\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.color.getAsHex(), currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SBoolData>) {\n                        return std::format(R\"#(     \"value\": {},\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.value, currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SChoiceData>) {\n                        return std::format(R\"#(     \"value\": \"{}\",\n        \"firstIndex\": {},\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.choices, val.firstIndex, currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SVectorData>) {\n                        return std::format(R\"#(     \"x\": {},\n        \"y\": {},\n        \"min_x\": {},\n        \"min_y\": {},\n        \"max_x\": {},\n        \"max_y\": {},\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y, currentValue, EXPLICIT);\n                    } else if constexpr (std::is_same_v<T, SGradientData>) {\n                        return std::format(R\"#(     \"value\": \"{}\",\n        \"current\": {},\n        \"explicit\": {})#\",\n                                           val.gradient, currentValue, EXPLICIT);\n                    }\n\n                } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, \"Bad any_cast on value {} in descriptions\", value); }\n                return std::string{\"\"};\n            },\n            data);\n    };\n\n    std::string json = std::format(R\"#({{\n    \"value\": \"{}\",\n    \"description\": \"{}\",\n    \"type\": {},\n    \"flags\": {},\n    \"data\": {{\n        {}\n    }}\n}})#\",\n                                   value, escapeJSONStrings(description), sc<uint16_t>(type), sc<uint32_t>(flags), parseData());\n\n    return json;\n}\n\nvoid CConfigManager::ensurePersistentWorkspacesPresent() {\n    g_pCompositor->ensurePersistentWorkspacesPresent(m_workspaceRules);\n}\n\nvoid CConfigManager::storeFloatingSize(PHLWINDOW window, const Vector2D& size) {\n    Log::logger->log(Log::DEBUG, \"storing floating size {}x{} for window {}::{}\", size.x, size.y, window->m_initialClass, window->m_initialTitle);\n    // true -> use m_initialClass and m_initialTitle\n    SFloatCache id{window, true};\n    m_mStoredFloatingSizes[id] = size;\n}\n\nstd::optional<Vector2D> CConfigManager::getStoredFloatingSize(PHLWINDOW window) {\n    // At startup, m_initialClass and m_initialTitle are undefined\n    // and m_class and m_title are just \"initial\" ones.\n    // false -> use m_class and m_title\n    SFloatCache id{window, false};\n    Log::logger->log(Log::DEBUG, \"Hash for window {}::{} = {}\", window->m_class, window->m_title, id.hash);\n    if (m_mStoredFloatingSizes.contains(id)) {\n        Log::logger->log(Log::DEBUG, \"got stored size {}x{} for window {}::{}\", m_mStoredFloatingSizes[id].x, m_mStoredFloatingSizes[id].y, window->m_class, window->m_title);\n        return m_mStoredFloatingSizes[id];\n    }\n    return std::nullopt;\n}\n"
  },
  {
    "path": "src/config/ConfigManager.hpp",
    "content": "#pragma once\n\n#include <hyprutils/animation/AnimationConfig.hpp>\n#define CONFIG_MANAGER_H\n\n#include <map>\n#include <unordered_map>\n#include \"../defines.hpp\"\n#include <variant>\n#include <vector>\n#include <optional>\n#include <functional>\n#include <xf86drmMode.h>\n#include \"../helpers/Monitor.hpp\"\n#include \"../desktop/view/Window.hpp\"\n\n#include \"ConfigDataValues.hpp\"\n#include \"../SharedDefs.hpp\"\n#include \"../helpers/Color.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"../desktop/reserved/ReservedArea.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../managers/XWaylandManager.hpp\"\n#include \"../managers/KeybindManager.hpp\"\n\n#include <hyprlang.hpp>\n\n#define HANDLE void*\n\nclass CConfigManager;\n\nstruct SWorkspaceRule {\n    std::string                        monitor         = \"\";\n    std::string                        workspaceString = \"\";\n    std::string                        workspaceName   = \"\";\n    WORKSPACEID                        workspaceId     = -1;\n    bool                               isDefault       = false;\n    bool                               isPersistent    = false;\n    std::optional<CCssGapData>         gapsIn;\n    std::optional<CCssGapData>         gapsOut;\n    std::optional<CCssGapData>         floatGaps = gapsOut;\n    std::optional<int64_t>             borderSize;\n    std::optional<bool>                decorate;\n    std::optional<bool>                noRounding;\n    std::optional<bool>                noBorder;\n    std::optional<bool>                noShadow;\n    std::optional<std::string>         onCreatedEmptyRunCmd;\n    std::optional<std::string>         defaultName;\n    std::optional<std::string>         layout;\n    std::map<std::string, std::string> layoutopts;\n    std::optional<std::string>         animationStyle;\n};\n\nstruct SPluginKeyword {\n    HANDLE                       handle = nullptr;\n    std::string                  name   = \"\";\n    Hyprlang::PCONFIGHANDLERFUNC fn     = nullptr;\n};\n\nstruct SPluginVariable {\n    HANDLE      handle = nullptr;\n    std::string name   = \"\";\n};\n\nenum eConfigOptionType : uint8_t {\n    CONFIG_OPTION_BOOL         = 0,\n    CONFIG_OPTION_INT          = 1, /* e.g. 0/1/2*/\n    CONFIG_OPTION_FLOAT        = 2,\n    CONFIG_OPTION_STRING_SHORT = 3, /* e.g. \"auto\" */\n    CONFIG_OPTION_STRING_LONG  = 4, /* e.g. a command */\n    CONFIG_OPTION_COLOR        = 5,\n    CONFIG_OPTION_CHOICE       = 6, /* e.g. \"one\", \"two\", \"three\" */\n    CONFIG_OPTION_GRADIENT     = 7,\n    CONFIG_OPTION_VECTOR       = 8,\n};\n\nenum eConfigOptionFlags : uint8_t {\n    CONFIG_OPTION_FLAG_PERCENTAGE = (1 << 0),\n};\n\nstruct SConfigOptionDescription {\n\n    struct SBoolData {\n        bool value = false;\n    };\n\n    struct SRangeData {\n        int value = 0, min = 0, max = 2;\n    };\n\n    struct SFloatData {\n        float value = 0, min = 0, max = 100;\n    };\n\n    struct SStringData {\n        std::string value;\n    };\n\n    struct SColorData {\n        CHyprColor color;\n    };\n\n    struct SChoiceData {\n        int         firstIndex = 0;\n        std::string choices; // comma-separated\n    };\n\n    struct SGradientData {\n        std::string gradient;\n    };\n\n    struct SVectorData {\n        Vector2D vec, min, max;\n    };\n\n    std::string       value; // e.g. general:gaps_in\n    std::string       description;\n    std::string       specialCategory; // if value is special (e.g. device:abc) value will be abc and special device\n    bool              specialKey = false;\n    eConfigOptionType type       = CONFIG_OPTION_BOOL;\n    uint32_t          flags      = 0; // eConfigOptionFlags\n\n    std::string       jsonify() const;\n\n    //\n    std::variant<SBoolData, SRangeData, SFloatData, SStringData, SColorData, SChoiceData, SGradientData, SVectorData> data;\n};\n\nstruct SFirstExecRequest {\n    std::string exec      = \"\";\n    bool        withRules = false;\n};\n\nstruct SFloatCache {\n    size_t hash;\n\n    SFloatCache(PHLWINDOW window, bool initial) {\n        // Base hash from class/title\n        size_t baseHash = initial ? (std::hash<std::string>{}(window->m_initialClass) ^ (std::hash<std::string>{}(window->m_initialTitle) << 1)) :\n                                    (std::hash<std::string>{}(window->m_class) ^ (std::hash<std::string>{}(window->m_title) << 1));\n\n        // Use empty string as default tag value\n        std::string tagValue = \"\";\n        if (auto xdgTag = window->xdgTag())\n            tagValue = xdgTag.value();\n\n        // Combine hashes\n        hash = baseHash ^ (std::hash<std::string>{}(tagValue) << 2);\n    }\n\n    bool operator==(const SFloatCache& other) const {\n        return hash == other.hash;\n    }\n};\n\nnamespace std {\n    template <>\n    struct hash<SFloatCache> {\n        size_t operator()(const SFloatCache& id) const {\n            return id.hash;\n        }\n    };\n}\n\nclass CMonitorRuleParser {\n  public:\n    CMonitorRuleParser(const std::string& name);\n\n    const std::string&         name();\n    SMonitorRule&              rule();\n    std::optional<std::string> getError();\n    bool                       parseMode(const std::string& value);\n    bool                       parsePosition(const std::string& value, bool isFirst = false);\n    bool                       parseScale(const std::string& value);\n    bool                       parseTransform(const std::string& value);\n    bool                       parseBitdepth(const std::string& value);\n    bool                       parseCM(const std::string& value);\n    bool                       parseSDRBrightness(const std::string& value);\n    bool                       parseSDRSaturation(const std::string& value);\n    bool                       parseVRR(const std::string& value);\n    bool                       parseICC(const std::string& value);\n\n    void                       setDisabled();\n    void                       setMirror(const std::string& value);\n    bool                       setReserved(const Desktop::CReservedArea& value);\n\n  private:\n    SMonitorRule m_rule;\n    std::string  m_error = \"\";\n};\n\nclass CConfigManager {\n  public:\n    CConfigManager();\n\n    void                                         init();\n    void                                         reload();\n    std::string                                  verify();\n\n    int                                          getDeviceInt(const std::string&, const std::string&, const std::string& fallback = \"\");\n    float                                        getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = \"\");\n    Vector2D                                     getDeviceVec(const std::string&, const std::string&, const std::string& fallback = \"\");\n    std::string                                  getDeviceString(const std::string&, const std::string&, const std::string& fallback = \"\");\n    bool                                         deviceConfigExplicitlySet(const std::string&, const std::string&);\n    bool                                         deviceConfigExists(const std::string&);\n    Hyprlang::CConfigValue*                      getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback);\n\n    void* const*                                 getConfigValuePtr(const std::string&);\n    Hyprlang::CConfigValue*                      getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = \"\");\n    std::string                                  getMainConfigPath();\n    std::string                                  getConfigString();\n\n    SMonitorRule                                 getMonitorRuleFor(const PHLMONITOR);\n    SWorkspaceRule                               getWorkspaceRuleFor(PHLWORKSPACE workspace);\n    std::string                                  getDefaultWorkspaceFor(const std::string&);\n\n    PHLMONITOR                                   getBoundMonitorForWS(const std::string&);\n    std::string                                  getBoundMonitorStringForWS(const std::string&);\n    const std::vector<SWorkspaceRule>&           getAllWorkspaceRules();\n\n    void                                         ensurePersistentWorkspacesPresent();\n\n    const std::vector<SConfigOptionDescription>& getAllDescriptions();\n\n    const std::unordered_map<std::string, SP<Hyprutils::Animation::SAnimationPropertyConfig>>& getAnimationConfig();\n\n    void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value);\n    void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {});\n    void removePluginConfig(HANDLE handle);\n\n    // no-op when done.\n    void                                               dispatchExecOnce();\n    void                                               dispatchExecShutdown();\n\n    void                                               performMonitorReload();\n    void                                               ensureMonitorStatus();\n    void                                               ensureVRR(PHLMONITOR pMonitor = nullptr);\n\n    bool                                               shouldUseSoftwareCursors(PHLMONITOR pMonitor);\n    void                                               updateWatcher();\n\n    std::string                                        parseKeyword(const std::string&, const std::string&);\n\n    void                                               addParseError(const std::string&);\n\n    SP<Hyprutils::Animation::SAnimationPropertyConfig> getAnimationPropertyConfig(const std::string&);\n\n    void                                               handlePluginLoads();\n    std::string                                        getErrors();\n\n    // keywords\n    std::optional<std::string> handleRawExec(const std::string&, const std::string&);\n    std::optional<std::string> handleExec(const std::string&, const std::string&);\n    std::optional<std::string> handleExecOnce(const std::string&, const std::string&);\n    std::optional<std::string> handleExecRawOnce(const std::string&, const std::string&);\n    std::optional<std::string> handleExecShutdown(const std::string&, const std::string&);\n    std::optional<std::string> handleMonitor(const std::string&, const std::string&);\n    std::optional<std::string> handleBind(const std::string&, const std::string&);\n    std::optional<std::string> handleUnbind(const std::string&, const std::string&);\n    std::optional<std::string> handleWorkspaceRules(const std::string&, const std::string&);\n    std::optional<std::string> handleBezier(const std::string&, const std::string&);\n    std::optional<std::string> handleAnimation(const std::string&, const std::string&);\n    std::optional<std::string> handleSource(const std::string&, const std::string&);\n    std::optional<std::string> handleSubmap(const std::string&, const std::string&);\n    std::optional<std::string> handleBindWS(const std::string&, const std::string&);\n    std::optional<std::string> handleEnv(const std::string&, const std::string&);\n    std::optional<std::string> handlePlugin(const std::string&, const std::string&);\n    std::optional<std::string> handlePermission(const std::string&, const std::string&);\n    std::optional<std::string> handleGesture(const std::string&, const std::string&);\n    std::optional<std::string> handleWindowrule(const std::string&, const std::string&);\n    std::optional<std::string> handleLayerrule(const std::string&, const std::string&);\n\n    std::optional<std::string> handleMonitorv2(const std::string& output);\n    Hyprlang::CParseResult     handleMonitorv2();\n    std::optional<std::string> addRuleFromConfigKey(const std::string& name);\n    std::optional<std::string> addLayerRuleFromConfigKey(const std::string& name);\n    Hyprlang::CParseResult     reloadRules();\n\n    std::string                m_configCurrentPath;\n\n    bool                       m_wantsMonitorReload                  = false;\n    bool                       m_noMonitorReload                     = false;\n    bool                       m_isLaunchingExecOnce                 = false; // For exec-once to skip initial ws tracking\n    bool                       m_lastConfigVerificationWasSuccessful = true;\n\n    void                       storeFloatingSize(PHLWINDOW window, const Vector2D& size);\n    std::optional<Vector2D>    getStoredFloatingSize(PHLWINDOW window);\n\n  private:\n    UP<Hyprlang::CConfig>                            m_config;\n\n    std::vector<std::string>                         m_configPaths;\n\n    Hyprutils::Animation::CAnimationConfigTree       m_animationTree;\n\n    SSubmap                                          m_currentSubmap;\n\n    std::vector<std::string>                         m_declaredPlugins;\n    std::vector<SPluginKeyword>                      m_pluginKeywords;\n    std::vector<SPluginVariable>                     m_pluginVariables;\n\n    std::vector<SP<Desktop::Rule::IRule>>            m_keywordRules;\n\n    bool                                             m_isFirstLaunch = true; // For exec-once\n\n    std::vector<SMonitorRule>                        m_monitorRules;\n    std::vector<SWorkspaceRule>                      m_workspaceRules;\n\n    bool                                             m_firstExecDispatched  = false;\n    bool                                             m_manualCrashInitiated = false;\n\n    std::vector<SFirstExecRequest>                   m_firstExecRequests; // bool is for if with rules\n    std::vector<std::string>                         m_finalExecRequests;\n\n    std::vector<std::pair<std::string, std::string>> m_failedPluginConfigValues; // for plugin values of unloaded plugins\n    std::string                                      m_configErrors = \"\";\n\n    uint32_t                                         m_configValueNumber = 0;\n\n    // internal methods\n    void                                      setDefaultAnimationVars();\n    std::optional<std::string>                resetHLConfig();\n    std::optional<std::string>                generateConfig(std::string configPath, bool safeMode = false);\n    std::optional<std::string>                verifyConfigExists();\n    void                                      reloadRuleConfigs();\n\n    void                                      postConfigReload(const Hyprlang::CParseResult& result);\n    SWorkspaceRule                            mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&);\n\n    void                                      registerConfigVar(const char* name, const Hyprlang::INT& val);\n    void                                      registerConfigVar(const char* name, const Hyprlang::FLOAT& val);\n    void                                      registerConfigVar(const char* name, const Hyprlang::VEC2& val);\n    void                                      registerConfigVar(const char* name, const Hyprlang::STRING& val);\n    void                                      registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val);\n\n    std::unordered_map<SFloatCache, Vector2D> m_mStoredFloatingSizes;\n\n    friend struct SConfigOptionDescription;\n    friend class CMonitorRuleParser;\n};\n\ninline UP<CConfigManager> g_pConfigManager;\n"
  },
  {
    "path": "src/config/ConfigValue.cpp",
    "content": "#include \"ConfigValue.hpp\"\n#include \"ConfigManager.hpp\"\n#include \"../macros.hpp\"\n\nvoid local__configValuePopulate(void* const** p, const std::string& val) {\n    const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);\n\n    *p = PVHYPRLANG->getDataStaticPtr();\n}\n\nstd::type_index local__configValueTypeIdx(const std::string& val) {\n    const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);\n    const auto ANY        = PVHYPRLANG->getValue();\n    return std::type_index(ANY.type());\n}"
  },
  {
    "path": "src/config/ConfigValue.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <typeindex>\n#include <hyprlang.hpp>\n#include \"../macros.hpp\"\n\n// giga hack to avoid including configManager here\n// NOLINTNEXTLINE\nvoid            local__configValuePopulate(void* const** p, const std::string& val);\nstd::type_index local__configValueTypeIdx(const std::string& val);\n\ntemplate <typename T>\nclass CConfigValue {\n  public:\n    CConfigValue(const std::string& val) {\n#ifdef HYPRLAND_DEBUG\n        // verify type\n        const auto TYPE = local__configValueTypeIdx(val);\n\n        // exceptions\n        const bool STRINGEX = (typeid(T) == typeid(std::string) && TYPE == typeid(Hyprlang::STRING));\n        const bool CUSTOMEX = (typeid(T) == typeid(Hyprlang::CUSTOMTYPE) && (TYPE == typeid(Hyprlang::CUSTOMTYPE*) || TYPE == typeid(void*) /* dunno why it does this? */));\n\n        RASSERT(typeid(T) == TYPE || STRINGEX || CUSTOMEX, \"Mismatched type in CConfigValue<T>, got {} but has {}\", typeid(T).name(), TYPE.name());\n#endif\n\n        local__configValuePopulate(&p_, val);\n    }\n\n    T* ptr() const {\n        return *rc<T* const*>(p_);\n    }\n\n    T operator*() const {\n        return *ptr();\n    }\n\n  private:\n    void* const* p_ = nullptr;\n};\n\ntemplate <>\ninline std::string* CConfigValue<std::string>::ptr() const {\n    RASSERT(false, \"Impossible to implement ptr() of CConfigValue<std::string>\");\n    return nullptr;\n}\n\ntemplate <>\ninline std::string CConfigValue<std::string>::operator*() const {\n    return std::string{*rc<const Hyprlang::STRING*>(p_)};\n}\n\ntemplate <>\ninline Hyprlang::STRING* CConfigValue<Hyprlang::STRING>::ptr() const {\n    return rc<Hyprlang::STRING*>(*p_);\n}\n\ntemplate <>\ninline Hyprlang::STRING CConfigValue<Hyprlang::STRING>::operator*() const {\n    return *rc<const Hyprlang::STRING*>(p_);\n}\n\ntemplate <>\ninline Hyprlang::CUSTOMTYPE* CConfigValue<Hyprlang::CUSTOMTYPE>::ptr() const {\n    return *rc<Hyprlang::CUSTOMTYPE* const*>(p_);\n}\n\ntemplate <>\ninline Hyprlang::CUSTOMTYPE CConfigValue<Hyprlang::CUSTOMTYPE>::operator*() const {\n    RASSERT(false, \"Impossible to implement operator* of CConfigValue<Hyprlang::CUSTOMTYPE>, use ptr()\");\n    return *ptr();\n}"
  },
  {
    "path": "src/config/ConfigWatcher.cpp",
    "content": "#include \"ConfigWatcher.hpp\"\n#if defined(__linux__)\n#include <linux/limits.h>\n#endif\n#include <sys/inotify.h>\n#include \"../debug/log/Logger.hpp\"\n#include <ranges>\n#include <fcntl.h>\n#include <unistd.h>\n#include <filesystem>\n\nusing namespace Hyprutils::OS;\n\nCConfigWatcher::CConfigWatcher() : m_inotifyFd(inotify_init()) {\n    if (!m_inotifyFd.isValid()) {\n        Log::logger->log(Log::ERR, \"CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded\");\n        return;\n    }\n\n    // TODO: make CFileDescriptor take F_GETFL, F_SETFL\n    const int FLAGS = fcntl(m_inotifyFd.get(), F_GETFL, 0);\n    if (fcntl(m_inotifyFd.get(), F_SETFL, FLAGS | O_NONBLOCK) < 0) {\n        Log::logger->log(Log::ERR, \"CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded\");\n        m_inotifyFd.reset();\n        return;\n    }\n}\n\nCFileDescriptor& CConfigWatcher::getInotifyFD() {\n    return m_inotifyFd;\n}\n\nvoid CConfigWatcher::setWatchList(const std::vector<std::string>& paths) {\n\n    // we clear all watches first, because whichever fired is now invalid\n    // or that is at least what it seems to be.\n    // since we don't know which fired,\n    // plus it doesn't matter that much, these ops are done rarely and fast anyways.\n\n    // cleanup old paths\n    for (auto& watch : m_watches) {\n        inotify_rm_watch(m_inotifyFd.get(), watch.wd);\n    }\n\n    m_watches.clear();\n\n    // add new paths\n    for (const auto& path : paths) {\n        m_watches.emplace_back(SInotifyWatch{\n            .wd   = inotify_add_watch(m_inotifyFd.get(), path.c_str(), IN_MODIFY | IN_DONT_FOLLOW),\n            .file = path,\n        });\n\n        std::error_code ec, ec2;\n        const auto      CANONICAL  = std::filesystem::canonical(path, ec);\n        const auto      IS_SYMLINK = std::filesystem::is_symlink(path, ec2);\n        if (!ec && !ec2 && IS_SYMLINK) {\n            m_watches.emplace_back(SInotifyWatch{\n                .wd   = inotify_add_watch(m_inotifyFd.get(), CANONICAL.c_str(), IN_MODIFY),\n                .file = path,\n            });\n        }\n    }\n}\n\nvoid CConfigWatcher::setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn) {\n    m_watchCallback = fn;\n}\n\nvoid CConfigWatcher::onInotifyEvent() {\n    constexpr size_t                                     BUFFER_SIZE = sizeof(inotify_event) + NAME_MAX + 1;\n    alignas(inotify_event) std::array<char, BUFFER_SIZE> buffer      = {};\n    const ssize_t                                        bytesRead   = read(m_inotifyFd.get(), buffer.data(), buffer.size());\n    if (bytesRead <= 0)\n        return;\n\n    for (size_t offset = 0; offset < sc<size_t>(bytesRead);) {\n        const auto* ev = rc<const inotify_event*>(buffer.data() + offset);\n\n        if (offset + sizeof(inotify_event) > sc<size_t>(bytesRead)) {\n            Log::logger->log(Log::ERR, \"CConfigWatcher: malformed inotify event, truncated header\");\n            break;\n        }\n\n        if (offset + sizeof(inotify_event) + ev->len > sc<size_t>(bytesRead)) {\n            Log::logger->log(Log::ERR, \"CConfigWatcher: malformed inotify event, truncated name field\");\n            break;\n        }\n\n        const auto WD = std::ranges::find_if(m_watches, [wd = ev->wd](const auto& e) { return e.wd == wd; });\n\n        if (WD == m_watches.end())\n            Log::logger->log(Log::ERR, \"CConfigWatcher: got an event for wd {} which we don't have?!\", ev->wd);\n        else\n            m_watchCallback(SConfigWatchEvent{\n                .file = WD->file,\n            });\n\n        offset += sizeof(inotify_event) + ev->len;\n    }\n}\n"
  },
  {
    "path": "src/config/ConfigWatcher.hpp",
    "content": "#pragma once\n#include \"../helpers/memory/Memory.hpp\"\n#include <vector>\n#include <string>\n#include <functional>\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CConfigWatcher {\n  public:\n    CConfigWatcher();\n    ~CConfigWatcher() = default;\n\n    struct SConfigWatchEvent {\n        std::string file;\n    };\n\n    Hyprutils::OS::CFileDescriptor& getInotifyFD();\n    void                            setWatchList(const std::vector<std::string>& paths);\n    void                            setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn);\n    void                            onInotifyEvent();\n\n  private:\n    struct SInotifyWatch {\n        int         wd = -1;\n        std::string file;\n    };\n\n    std::function<void(const SConfigWatchEvent&)> m_watchCallback;\n    std::vector<SInotifyWatch>                    m_watches;\n    Hyprutils::OS::CFileDescriptor                m_inotifyFd;\n};\n\ninline UP<CConfigWatcher> g_pConfigWatcher = makeUnique<CConfigWatcher>();\n"
  },
  {
    "path": "src/config/defaultConfig.hpp",
    "content": "#pragma once\n\n#include <string>\n\ninline constexpr std::string_view AUTOGENERATED_PREFIX   = R\"#(\n# #######################################################################################\n# AUTOGENERATED HYPRLAND CONFIG.\n# EDIT THIS CONFIG ACCORDING TO THE WIKI INSTRUCTIONS.\n# #######################################################################################\n\nautogenerated = 1 # remove this line to remove the warning\n\n)#\";\ninline constexpr char             EXAMPLE_CONFIG_BYTES[] = {\n#embed \"../../example/hyprland.conf\"\n};\n\ninline constexpr std::string_view EXAMPLE_CONFIG = {EXAMPLE_CONFIG_BYTES, sizeof(EXAMPLE_CONFIG_BYTES)};\n"
  },
  {
    "path": "src/debug/HyprCtl.cpp",
    "content": "#include \"HyprCtl.hpp\"\n#include \"helpers/Monitor.hpp\"\n\n#include <algorithm>\n#include <format>\n#include <fstream>\n#include <iterator>\n#include <netinet/in.h>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/utsname.h>\n#include <sys/un.h>\n#include <unistd.h>\n#include <sys/poll.h>\n#include <filesystem>\n#include <ranges>\n#include <sys/eventfd.h>\n\n#include <sstream>\n#include <string>\n#include <typeindex>\n#include <numeric>\n\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::OS;\n#include <aquamarine/input/Input.hpp>\n\n#include \"../config/ConfigDataValues.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../managers/CursorManager.hpp\"\n#include \"../hyprerror/HyprError.hpp\"\n#include \"../devices/IPointer.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../devices/ITouch.hpp\"\n#include \"../devices/Tablet.hpp\"\n#include \"../protocols/GlobalShortcuts.hpp\"\n#include \"debug/log/RollingLogFollow.hpp\"\n#include \"config/ConfigManager.hpp\"\n#include \"helpers/MiscFunctions.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"../desktop/view/Group.hpp\"\n#include \"../desktop/rule/Engine.hpp\"\n#include \"../desktop/history/WindowHistoryTracker.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../version.h\"\n\n#include \"../Compositor.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/XWaylandManager.hpp\"\n#include \"../plugins/PluginSystem.hpp\"\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../debug/HyprNotificationOverlay.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../render/OpenGL.hpp\"\n#include \"../layout/space/Space.hpp\"\n#include \"../layout/algorithm/Algorithm.hpp\"\n#include \"../layout/algorithm/TiledAlgorithm.hpp\"\n#include \"../layout/supplementary/WorkspaceAlgoMatcher.hpp\"\n\n#if defined(__DragonFly__) || defined(__FreeBSD__)\n#include <sys/ucred.h>\n#define CRED_T   xucred\n#define CRED_LVL SOL_LOCAL\n#define CRED_OPT LOCAL_PEERCRED\n#define CRED_PID cr_pid\n#elif defined(__NetBSD__)\n#define CRED_T   unpcbid\n#define CRED_LVL SOL_LOCAL\n#define CRED_OPT LOCAL_PEEREID\n#define CRED_PID unp_pid\n#else\n#if defined(__OpenBSD__)\n#define CRED_T sockpeercred\n#else\n#define CRED_T ucred\n#endif\n#define CRED_LVL SOL_SOCKET\n#define CRED_OPT SO_PEERCRED\n#define CRED_PID pid\n#endif\n\nstatic void trimTrailingComma(std::string& str) {\n    if (!str.empty() && str.back() == ',')\n        str.pop_back();\n}\n\nstatic std::string formatToString(uint32_t drmFormat) {\n    switch (drmFormat) {\n        case DRM_FORMAT_XRGB2101010: return \"XRGB2101010\";\n        case DRM_FORMAT_XBGR2101010: return \"XBGR2101010\";\n        case DRM_FORMAT_XRGB8888: return \"XRGB8888\";\n        case DRM_FORMAT_XBGR8888: return \"XBGR8888\";\n        default: break;\n    }\n\n    return \"Invalid\";\n}\n\nstatic std::string availableModesForOutput(PHLMONITOR pMonitor, eHyprCtlOutputFormat format) {\n    std::string result;\n\n    for (auto const& m : pMonitor->m_output->modes) {\n        if (format == FORMAT_NORMAL)\n            result += std::format(\"{}x{}@{:.2f}Hz \", m->pixelSize.x, m->pixelSize.y, m->refreshRate / 1000.0);\n        else\n            result += std::format(\"\\\"{}x{}@{:.2f}Hz\\\",\", m->pixelSize.x, m->pixelSize.y, m->refreshRate / 1000.0);\n    }\n\n    trimTrailingComma(result);\n\n    return result;\n}\n\nconst std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_JSON = {\n    \"\\\"UNKNOWN\\\"\",   \"\\\"NOTIFICATION\\\"\", \"\\\"LOCK\\\"\",      \"\\\"WORKSPACE\\\"\", \"\\\"WINDOWED\\\"\", \"\\\"DND\\\"\",        \"\\\"SPECIAL\\\"\",  \"\\\"ALPHA\\\"\",       \"\\\"OFFSET\\\"\",\n    \"\\\"CANDIDATE\\\"\", \"\\\"OPAQUE\\\"\",       \"\\\"TRANSFORM\\\"\", \"\\\"OVERLAYS\\\"\",  \"\\\"FLOAT\\\"\",    \"\\\"WORKSPACES\\\"\", \"\\\"SURFACES\\\"\", \"\\\"CONFIGERROR\\\"\",\n};\n\nconst std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_TEXT = {\n    \"unknown reason\",    \"notification\",     \"session lock\",     \"invalid workspace\", \"windowed mode\", \"dnd active\",\n    \"special workspace\", \"alpha channel\",    \"workspace offset\", \"missing candidate\", \"not opaque\",    \"surface transformations\",\n    \"other overlays\",    \"floating windows\", \"other workspaces\", \"subsurfaces\",       \"config error\",\n};\n\nstd::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {\n    const auto reasons = m->isSolitaryBlocked(true);\n    if (!reasons)\n        return \"null\";\n\n    std::string reasonStr = \"\";\n    const auto  TEXTS     = format == eHyprCtlOutputFormat::FORMAT_JSON ? SOLITARY_REASONS_JSON : SOLITARY_REASONS_TEXT;\n\n    for (uint32_t i = 0; i < CMonitor::SC_CHECKS_COUNT; i++) {\n        if (reasons & (1 << i)) {\n            if (reasonStr != \"\")\n                reasonStr += \",\";\n            reasonStr += TEXTS[i];\n        }\n    }\n\n    return format == eHyprCtlOutputFormat::FORMAT_JSON ? \"[\" + reasonStr + \"]\" : reasonStr;\n}\n\nconst std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_JSON = {\n    \"\\\"UNKNOWN\\\"\",   \"\\\"USER\\\"\",    \"\\\"WINDOWED\\\"\",  \"\\\"CONTENT\\\"\", \"\\\"MIRROR\\\"\", \"\\\"RECORD\\\"\", \"\\\"SW\\\"\",\n    \"\\\"CANDIDATE\\\"\", \"\\\"SURFACE\\\"\", \"\\\"TRANSFORM\\\"\", \"\\\"DMA\\\"\",     \"\\\"FAILED\\\"\", \"\\\"CM\\\"\",\n};\n\nconst std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_TEXT = {\n    \"unknown reason\",    \"user settings\",   \"windowed mode\",           \"content type\",   \"monitor mirrors\",   \"screen record/screenshot\", \"software renders/cursors\",\n    \"missing candidate\", \"invalid surface\", \"surface transformations\", \"invalid buffer\", \"activation failed\", \"color management\",\n};\n\nstd::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {\n    const auto reasons = m->isDSBlocked(true);\n    if (!reasons)\n        return \"null\";\n\n    std::string reasonStr = \"\";\n    const auto  TEXTS     = format == eHyprCtlOutputFormat::FORMAT_JSON ? DS_REASONS_JSON : DS_REASONS_TEXT;\n\n    for (int i = 0; i < CMonitor::DS_CHECKS_COUNT; i++) {\n        if (reasons & (1 << i)) {\n            if (reasonStr != \"\")\n                reasonStr += \",\";\n            reasonStr += TEXTS[i];\n        }\n    }\n\n    return format == eHyprCtlOutputFormat::FORMAT_JSON ? \"[\" + reasonStr + \"]\" : reasonStr;\n}\n\nconst std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_JSON = {\n    \"\\\"UNKNOWN\\\"\", \"\\\"NOT_TORN\\\"\", \"\\\"USER\\\"\", \"\\\"ZOOM\\\"\", \"\\\"SUPPORT\\\"\", \"\\\"CANDIDATE\\\"\", \"\\\"WINDOW\\\"\", \"\\\"HW_CURSOR\\\"\",\n};\n\nconst std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_TEXT = {\"unknown reason\",           \"next frame is not torn\", \"user settings\",   \"zoom\",\n                                                                                 \"not supported by monitor\", \"missing candidate\",      \"window settings\", \"hw cursor\"};\n\nstd::string                                              CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {\n    const auto reasons = m->isTearingBlocked(true);\n    if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing))\n        return \"null\";\n\n    std::string reasonStr = \"\";\n    const auto  TEXTS     = format == eHyprCtlOutputFormat::FORMAT_JSON ? TEARING_REASONS_JSON : TEARING_REASONS_TEXT;\n\n    for (int i = 0; i < CMonitor::TC_CHECKS_COUNT; i++) {\n        if (reasons & (1 << i)) {\n            if (reasonStr != \"\")\n                reasonStr += \",\";\n            reasonStr += TEXTS[i];\n        }\n    }\n\n    return format == eHyprCtlOutputFormat::FORMAT_JSON ? \"[\" + reasonStr + \"]\" : reasonStr;\n}\n\nstd::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {\n    std::string result;\n    if (!m->m_output || m->m_id == -1)\n        return \"\";\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n\n        result += std::format(\n            R\"#({{\n    \"id\": {},\n    \"name\": \"{}\",\n    \"description\": \"{}\",\n    \"make\": \"{}\",\n    \"model\": \"{}\",\n    \"serial\": \"{}\",\n    \"width\": {},\n    \"height\": {},\n    \"physicalWidth\": {},\n    \"physicalHeight\": {},\n    \"refreshRate\": {:.5f},\n    \"x\": {},\n    \"y\": {},\n    \"activeWorkspace\": {{\n        \"id\": {},\n        \"name\": \"{}\"\n    }},\n    \"specialWorkspace\": {{\n        \"id\": {},\n        \"name\": \"{}\"\n    }},\n    \"reserved\": [{}, {}, {}, {}],\n    \"scale\": {:.2f},\n    \"transform\": {},\n    \"focused\": {},\n    \"dpmsStatus\": {},\n    \"vrr\": {},\n    \"solitary\": \"{:x}\",\n    \"solitaryBlockedBy\": {},\n    \"activelyTearing\": {},\n    \"tearingBlockedBy\": {},\n    \"directScanoutTo\": \"{:x}\",\n    \"directScanoutBlockedBy\": {},\n    \"disabled\": {},\n    \"currentFormat\": \"{}\",\n    \"mirrorOf\": \"{}\",\n    \"availableModes\": [{}],\n    \"colorManagementPreset\": \"{}\",\n    \"sdrBrightness\": {:.2f},\n    \"sdrSaturation\": {:.2f},\n    \"sdrMinLuminance\": {:.2f},\n    \"sdrMaxLuminance\": {}\n}},)#\",\n\n            m->m_id, escapeJSONStrings(m->m_name), escapeJSONStrings(m->m_shortDescription), escapeJSONStrings(m->m_output->make), escapeJSONStrings(m->m_output->model),\n            escapeJSONStrings(m->m_output->serial), sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), sc<int>(m->m_output->physicalSize.x),\n            sc<int>(m->m_output->physicalSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->activeWorkspaceID(),\n            (!m->m_activeWorkspace ? \"\" : escapeJSONStrings(m->m_activeWorkspace->m_name)), m->activeSpecialWorkspaceID(),\n            escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : \"\"), sc<int>(m->m_reservedArea.left()), sc<int>(m->m_reservedArea.top()),\n            sc<int>(m->m_reservedArea.right()), sc<int>(m->m_reservedArea.bottom()), m->m_scale, sc<int>(m->m_transform),\n            (m == Desktop::focusState()->monitor() ? \"true\" : \"false\"), (m->m_dpmsStatus ? \"true\" : \"false\"), (m->m_output->state->state().adaptiveSync ? \"true\" : \"false\"),\n            rc<uint64_t>(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? \"true\" : \"false\"),\n            getTearingBlockedReason(m, format), rc<uint64_t>(m->m_lastScanout.get()), getDSBlockedReason(m, format), (m->m_enabled ? \"false\" : \"true\"),\n            formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format(\"{}\", m->m_mirrorOf->m_id) : \"none\", availableModesForOutput(m, format),\n            (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance));\n\n    } else {\n        result += std::format(\n            \"Monitor {} (ID {}):\\n\\t{}x{}@{:.5f} at {}x{}\\n\\tdescription: {}\\n\\tmake: {}\\n\\tmodel: {}\\n\\tphysical size (mm): {}x{}\\n\\tserial: {}\\n\\tactive workspace: {} ({})\\n\\t\"\n            \"special workspace: {} ({})\\n\\treserved: {} {} {} {}\\n\\tscale: {:.2f}\\n\\ttransform: {}\\n\\tfocused: {}\\n\\t\"\n            \"dpmsStatus: {}\\n\\tvrr: {}\\n\\tsolitary: {:x}\\n\\tsolitaryBlockedBy: {}\\n\\tactivelyTearing: {}\\n\\ttearingBlockedBy: {}\\n\\tdirectScanoutTo: \"\n            \"{:x}\\n\\tdirectScanoutBlockedBy: {}\\n\\tdisabled: \"\n            \"{}\\n\\tcurrentFormat: {}\\n\\tmirrorOf: \"\n            \"{}\\n\\tavailableModes: {}\\n\\tcolorManagementPreset: {}\\n\\tsdrBrightness: {:.2f}\\n\\tsdrSaturation: {:.2f}\\n\\tsdrMinLuminance: {:.2f}\\n\\tsdrMaxLuminance: {}\\n\\n\",\n            m->m_name, m->m_id, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->m_shortDescription,\n            m->m_output->make, m->m_output->model, sc<int>(m->m_output->physicalSize.x), sc<int>(m->m_output->physicalSize.y), m->m_output->serial, m->activeWorkspaceID(),\n            (!m->m_activeWorkspace ? \"\" : m->m_activeWorkspace->m_name), m->activeSpecialWorkspaceID(), (m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : \"\"),\n            sc<int>(m->m_reservedArea.left()), sc<int>(m->m_reservedArea.top()), sc<int>(m->m_reservedArea.right()), sc<int>(m->m_reservedArea.bottom()), m->m_scale,\n            sc<int>(m->m_transform), (m == Desktop::focusState()->monitor() ? \"yes\" : \"no\"), sc<int>(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync,\n            rc<uint64_t>(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format),\n            rc<uint64_t>(m->m_lastScanout.get()), getDSBlockedReason(m, format), !m->m_enabled, formatToString(m->m_output->state->state().drmFormat),\n            m->m_mirrorOf ? std::format(\"{}\", m->m_mirrorOf->m_id) : \"none\", availableModesForOutput(m, format), (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness),\n            (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance));\n    }\n\n    return result;\n}\n\nstatic std::string monitorsRequest(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n    auto     allMonitors = false;\n\n    if (vars.size() > 2)\n        return \"too many args\";\n\n    if (vars.size() == 2 && vars[1] == \"all\")\n        allMonitors = true;\n\n    std::string result = \"\";\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\";\n\n        for (auto const& m : allMonitors ? g_pCompositor->m_realMonitors : g_pCompositor->m_monitors) {\n            result += CHyprCtl::getMonitorData(m, format);\n        }\n\n        trimTrailingComma(result);\n\n        result += \"]\";\n    } else {\n        for (auto const& m : allMonitors ? g_pCompositor->m_realMonitors : g_pCompositor->m_monitors) {\n            if (!m->m_output || m->m_id == -1)\n                continue;\n\n            result += CHyprCtl::getMonitorData(m, format);\n        }\n    }\n\n    return result;\n}\n\nstatic std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) {\n    const auto tags = w->m_ruleApplicator->m_tagKeeper.getTags();\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON)\n        return std::ranges::fold_left(tags, std::string(),\n                                      [](const std::string& a, const std::string& b) { return a.empty() ? std::format(\"\\\"{}\\\"\", b) : std::format(\"{}, \\\"{}\\\"\", a, b); });\n    else\n        return std::ranges::fold_left(tags, std::string(), [](const std::string& a, const std::string& b) { return a.empty() ? b : a + \", \" + b; });\n}\n\nstatic std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) {\n    const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON;\n    if (!w->m_group)\n        return isJson ? \"\" : \"0\";\n\n    std::ostringstream result;\n\n    for (const auto& curr : w->m_group->windows()) {\n        if (isJson)\n            result << std::format(\"\\\"0x{:x}\\\"\", rc<uintptr_t>(curr.get()));\n        else\n            result << std::format(\"{:x}\", rc<uintptr_t>(curr.get()));\n\n        if (curr != w->m_group->windows().back())\n            result << (isJson ? \", \" : \",\");\n    }\n\n    return result.str();\n}\n\nstd::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {\n    auto getFocusHistoryID = [](PHLWINDOW wnd) -> int {\n        const auto& HISTORY = Desktop::History::windowTracker()->fullHistory();\n        for (size_t i = 0; i < HISTORY.size(); ++i) {\n            if (HISTORY[i].lock() == wnd)\n                return HISTORY.size() - i - 1; // reverse order for backwards compat\n        }\n        return -1;\n    };\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        return std::format(\n            R\"#({{\n    \"address\": \"0x{:x}\",\n    \"mapped\": {},\n    \"hidden\": {},\n    \"at\": [{}, {}],\n    \"size\": [{}, {}],\n    \"workspace\": {{\n        \"id\": {},\n        \"name\": \"{}\"\n    }},\n    \"floating\": {},\n    \"monitor\": {},\n    \"class\": \"{}\",\n    \"title\": \"{}\",\n    \"initialClass\": \"{}\",\n    \"initialTitle\": \"{}\",\n    \"pid\": {},\n    \"xwayland\": {},\n    \"pinned\": {},\n    \"fullscreen\": {},\n    \"fullscreenClient\": {},\n    \"overFullscreen\": {},\n    \"grouped\": [{}],\n    \"tags\": [{}],\n    \"swallowing\": \"0x{:x}\",\n    \"focusHistoryID\": {},\n    \"inhibitingIdle\": {},\n    \"xdgTag\": \"{}\",\n    \"xdgDescription\": \"{}\",\n    \"contentType\": \"{}\",\n    \"stableId\": \"{:x}\"\n}},)#\",\n            rc<uintptr_t>(w.get()), (w->m_isMapped ? \"true\" : \"false\"), (w->isHidden() ? \"true\" : \"false\"), sc<int>(w->m_realPosition->goal().x),\n            sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,\n            escapeJSONStrings(!w->m_workspace ? \"\" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? \"true\" : \"false\"), w->monitorID(), escapeJSONStrings(w->m_class),\n            escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc<int>(w->m_isX11) == 1 ? \"true\" : \"false\"),\n            (w->m_pinned ? \"true\" : \"false\"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? \"true\" : \"false\"),\n            getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),\n            (g_pInputManager->isWindowInhibiting(w, false) ? \"true\" : \"false\"), escapeJSONStrings(w->xdgTag().value_or(\"\")), escapeJSONStrings(w->xdgDescription().value_or(\"\")),\n            escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID);\n    } else {\n        return std::format(\n            \"Window {:x} -> {}:\\n\\tmapped: {}\\n\\thidden: {}\\n\\tat: {},{}\\n\\tsize: {},{}\\n\\tworkspace: {} ({})\\n\\tfloating: {}\\n\\tmonitor: {}\\n\\tclass: {}\\n\\ttitle: \"\n            \"{}\\n\\tinitialClass: {}\\n\\tinitialTitle: {}\\n\\tpid: \"\n            \"{}\\n\\txwayland: {}\\n\\tpinned: \"\n            \"{}\\n\\tfullscreen: {}\\n\\tfullscreenClient: {}\\n\\toverFullscreen: {}\\n\\tgrouped: {}\\n\\ttags: {}\\n\\tswallowing: {:x}\\n\\tfocusHistoryID: {}\\n\\tinhibitingIdle: \"\n            \"{}\\n\\txdgTag: \"\n            \"{}\\n\\txdgDescription: {}\\n\\tcontentType: {}\\n\\tstableID: {:x}\\n\\n\",\n            rc<uintptr_t>(w.get()), w->m_title, sc<int>(w->m_isMapped), sc<int>(w->isHidden()), sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y),\n            sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,\n            (!w->m_workspace ? \"\" : w->m_workspace->m_name), sc<int>(w->m_isFloating), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(),\n            sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), sc<int>(w->m_createdOverFullscreen),\n            getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)),\n            w->xdgTag().value_or(\"\"), w->xdgDescription().value_or(\"\"), NContentType::toString(w->getContentType()), w->m_stableID);\n    }\n}\n\nstatic std::string clientsRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = \"\";\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\";\n\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped && !g_pHyprCtl->m_currentRequestParams.all)\n                continue;\n\n            result += CHyprCtl::getWindowData(w, format);\n        }\n\n        trimTrailingComma(result);\n\n        result += \"]\";\n    } else {\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped && !g_pHyprCtl->m_currentRequestParams.all)\n                continue;\n\n            result += CHyprCtl::getWindowData(w, format);\n        }\n\n        if (result.empty())\n            return \"no open windows\";\n    }\n    return result;\n}\n\nstd::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) {\n    const auto  PLASTW   = w->getLastFocusedWindow();\n    const auto  PMONITOR = w->m_monitor.lock();\n\n    std::string layoutName = \"unknown\";\n    if (w->m_space && w->m_space->algorithm() && w->m_space->algorithm()->tiledAlgo()) {\n        const auto& TILED_ALGO = w->m_space->algorithm()->tiledAlgo();\n        layoutName             = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get()));\n    }\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        return std::format(R\"#({{\n    \"id\": {},\n    \"name\": \"{}\",\n    \"monitor\": \"{}\",\n    \"monitorID\": {},\n    \"windows\": {},\n    \"hasfullscreen\": {},\n    \"lastwindow\": \"0x{:x}\",\n    \"lastwindowtitle\": \"{}\",\n    \"ispersistent\": {},\n    \"tiledLayout\": \"{}\"\n}})#\",\n                           w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : \"?\"),\n                           escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : \"null\"), w->getWindows(), w->m_hasFullscreenWindow ? \"true\" : \"false\",\n                           rc<uintptr_t>(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : \"\", w->isPersistent() ? \"true\" : \"false\", escapeJSONStrings(layoutName));\n    } else {\n        return std::format(\"workspace ID {} ({}) on monitor {}:\\n\\tmonitorID: {}\\n\\twindows: {}\\n\\thasfullscreen: {}\\n\\tlastwindow: 0x{:x}\\n\\tlastwindowtitle: {}\\n\\tispersistent: \"\n                           \"{}\\n\\ttiledLayout: {}\\n\\n\",\n                           w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : \"?\", PMONITOR ? std::to_string(PMONITOR->m_id) : \"null\", w->getWindows(),\n                           sc<int>(w->m_hasFullscreenWindow), rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : \"\", sc<int>(w->isPersistent()), layoutName);\n    }\n}\n\nstatic std::string getWorkspaceRuleData(const SWorkspaceRule& r, eHyprCtlOutputFormat format) {\n    const auto boolToString = [](const bool b) -> std::string { return b ? \"true\" : \"false\"; };\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        const std::string monitor     = r.monitor.empty() ? \"\" : std::format(\",\\n    \\\"monitor\\\": \\\"{}\\\"\", escapeJSONStrings(r.monitor));\n        const std::string default_    = sc<bool>(r.isDefault) ? std::format(\",\\n    \\\"default\\\": {}\", boolToString(r.isDefault)) : \"\";\n        const std::string persistent  = sc<bool>(r.isPersistent) ? std::format(\",\\n    \\\"persistent\\\": {}\", boolToString(r.isPersistent)) : \"\";\n        const std::string gapsIn      = sc<bool>(r.gapsIn) ?\n            std::format(\",\\n    \\\"gapsIn\\\": [{}, {}, {}, {}]\", r.gapsIn.value().m_top, r.gapsIn.value().m_right, r.gapsIn.value().m_bottom, r.gapsIn.value().m_left) :\n            \"\";\n        const std::string gapsOut     = sc<bool>(r.gapsOut) ?\n            std::format(\",\\n    \\\"gapsOut\\\": [{}, {}, {}, {}]\", r.gapsOut.value().m_top, r.gapsOut.value().m_right, r.gapsOut.value().m_bottom, r.gapsOut.value().m_left) :\n            \"\";\n        const std::string borderSize  = sc<bool>(r.borderSize) ? std::format(\",\\n    \\\"borderSize\\\": {}\", r.borderSize.value()) : \"\";\n        const std::string border      = sc<bool>(r.noBorder) ? std::format(\",\\n    \\\"border\\\": {}\", boolToString(!r.noBorder.value())) : \"\";\n        const std::string rounding    = sc<bool>(r.noRounding) ? std::format(\",\\n    \\\"rounding\\\": {}\", boolToString(!r.noRounding.value())) : \"\";\n        const std::string decorate    = sc<bool>(r.decorate) ? std::format(\",\\n    \\\"decorate\\\": {}\", boolToString(r.decorate.value())) : \"\";\n        const std::string shadow      = sc<bool>(r.noShadow) ? std::format(\",\\n    \\\"shadow\\\": {}\", boolToString(!r.noShadow.value())) : \"\";\n        const std::string defaultName = r.defaultName.has_value() ? std::format(\",\\n    \\\"defaultName\\\": \\\"{}\\\"\", escapeJSONStrings(r.defaultName.value())) : \"\";\n\n        std::string       result =\n            std::format(R\"#({{\n    \"workspaceString\": \"{}\"{}{}{}{}{}{}{}{}{}{}{}\n}})#\",\n                        escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, borderSize, border, rounding, decorate, shadow, defaultName);\n\n        return result;\n    } else {\n        const std::string monitor    = std::format(\"\\tmonitor: {}\\n\", r.monitor.empty() ? \"<unset>\" : escapeJSONStrings(r.monitor));\n        const std::string default_   = std::format(\"\\tdefault: {}\\n\", sc<bool>(r.isDefault) ? boolToString(r.isDefault) : \"<unset>\");\n        const std::string persistent = std::format(\"\\tpersistent: {}\\n\", sc<bool>(r.isPersistent) ? boolToString(r.isPersistent) : \"<unset>\");\n        const std::string gapsIn     = sc<bool>(r.gapsIn) ? std::format(\"\\tgapsIn: {} {} {} {}\\n\", std::to_string(r.gapsIn.value().m_top), std::to_string(r.gapsIn.value().m_right),\n                                                                        std::to_string(r.gapsIn.value().m_bottom), std::to_string(r.gapsIn.value().m_left)) :\n                                                            std::format(\"\\tgapsIn: <unset>\\n\");\n        const std::string gapsOut    = sc<bool>(r.gapsOut) ?\n            std::format(\"\\tgapsOut: {} {} {} {}\\n\", std::to_string(r.gapsOut.value().m_top), std::to_string(r.gapsOut.value().m_right), std::to_string(r.gapsOut.value().m_bottom),\n                        std::to_string(r.gapsOut.value().m_left)) :\n            std::format(\"\\tgapsOut: <unset>\\n\");\n        const std::string borderSize = std::format(\"\\tborderSize: {}\\n\", sc<bool>(r.borderSize) ? std::to_string(r.borderSize.value()) : \"<unset>\");\n        const std::string border     = std::format(\"\\tborder: {}\\n\", sc<bool>(r.noBorder) ? boolToString(!r.noBorder.value()) : \"<unset>\");\n        const std::string rounding   = std::format(\"\\trounding: {}\\n\", sc<bool>(r.noRounding) ? boolToString(!r.noRounding.value()) : \"<unset>\");\n        const std::string decorate   = std::format(\"\\tdecorate: {}\\n\", sc<bool>(r.decorate) ? boolToString(r.decorate.value()) : \"<unset>\");\n        const std::string shadow     = std::format(\"\\tshadow: {}\\n\", sc<bool>(r.noShadow) ? boolToString(!r.noShadow.value()) : \"<unset>\");\n        const std::string defaultName = std::format(\"\\tdefaultName: {}\\n\", r.defaultName.value_or(\"<unset>\"));\n\n        std::string       result = std::format(\"Workspace rule {}:\\n{}{}{}{}{}{}{}{}{}{}{}\\n\", escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut,\n                                               borderSize, border, rounding, decorate, shadow, defaultName);\n\n        return result;\n    }\n}\n\nstatic std::string activeWorkspaceRequest(eHyprCtlOutputFormat format, std::string request) {\n    if (!Desktop::focusState()->monitor())\n        return \"unsafe state\";\n\n    std::string result = \"\";\n    auto        w      = Desktop::focusState()->monitor()->m_activeWorkspace;\n\n    if (!valid(w))\n        return \"internal error\";\n\n    return CHyprCtl::getWorkspaceData(w, format);\n}\n\nstatic std::string workspacesRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = \"\";\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\";\n        for (auto const& w : g_pCompositor->getWorkspaces()) {\n            result += CHyprCtl::getWorkspaceData(w.lock(), format);\n            result += \",\";\n        }\n\n        trimTrailingComma(result);\n        result += \"]\";\n    } else {\n        for (auto const& w : g_pCompositor->getWorkspaces()) {\n            result += CHyprCtl::getWorkspaceData(w.lock(), format);\n        }\n    }\n\n    return result;\n}\n\nstatic std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = \"\";\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\";\n        for (auto const& r : g_pConfigManager->getAllWorkspaceRules()) {\n            result += getWorkspaceRuleData(r, format);\n            result += \",\";\n        }\n\n        trimTrailingComma(result);\n        result += \"]\";\n    } else {\n        for (auto const& r : g_pConfigManager->getAllWorkspaceRules()) {\n            result += getWorkspaceRuleData(r, format);\n        }\n    }\n\n    return result;\n}\n\nstatic std::string activeWindowRequest(eHyprCtlOutputFormat format, std::string request) {\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!validMapped(PWINDOW))\n        return format == eHyprCtlOutputFormat::FORMAT_JSON ? \"{}\" : \"Invalid\";\n\n    auto result = CHyprCtl::getWindowData(PWINDOW, format);\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON)\n        result.pop_back();\n\n    return result;\n}\n\nstatic std::string layersRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = \"\";\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"{\\n\";\n\n        for (auto const& mon : g_pCompositor->m_monitors) {\n            result += std::format(\n                R\"#(\"{}\": {{\n    \"levels\": {{\n)#\",\n                escapeJSONStrings(mon->m_name));\n\n            int layerLevel = 0;\n            for (auto const& level : mon->m_layerSurfaceLayers) {\n                result += std::format(\n                    R\"#(\n        \"{}\": [\n)#\",\n                    layerLevel);\n                for (auto const& layer : level) {\n                    result += std::format(\n                        R\"#(                {{\n                    \"address\": \"0x{:x}\",\n                    \"x\": {},\n                    \"y\": {},\n                    \"w\": {},\n                    \"h\": {},\n                    \"namespace\": \"{}\",\n                    \"pid\": {}\n                }},)#\",\n                        rc<uintptr_t>(layer.get()), layer->m_geometry.x, layer->m_geometry.y, layer->m_geometry.width, layer->m_geometry.height,\n                        escapeJSONStrings(layer->m_namespace), layer->getPID());\n                }\n\n                trimTrailingComma(result);\n\n                if (!level.empty())\n                    result += \"\\n        \";\n\n                result += \"],\";\n\n                layerLevel++;\n            }\n\n            trimTrailingComma(result);\n\n            result += \"\\n    }\\n},\";\n        }\n\n        trimTrailingComma(result);\n\n        result += \"\\n}\\n\";\n\n    } else {\n        for (auto const& mon : g_pCompositor->m_monitors) {\n            result += std::format(\"Monitor {}:\\n\", mon->m_name);\n            int                                     layerLevel = 0;\n            static const std::array<std::string, 4> levelNames = {\"background\", \"bottom\", \"top\", \"overlay\"};\n            for (auto const& level : mon->m_layerSurfaceLayers) {\n                result += std::format(\"\\tLayer level {} ({}):\\n\", layerLevel, levelNames[layerLevel]);\n\n                for (auto const& layer : level) {\n                    result += std::format(\"\\t\\tLayer {:x}: xywh: {} {} {} {}, namespace: {}, pid: {}\\n\", rc<uintptr_t>(layer.get()), layer->m_geometry.x, layer->m_geometry.y,\n                                          layer->m_geometry.width, layer->m_geometry.height, layer->m_namespace, layer->getPID());\n                }\n\n                layerLevel++;\n            }\n            result += \"\\n\\n\";\n        }\n    }\n\n    return result;\n}\n\nstatic std::string configErrorsRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result     = \"\";\n    std::string currErrors = g_pConfigManager->getErrors();\n    CVarList    errLines(currErrors, 0, '\\n');\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\";\n        for (const auto& line : errLines) {\n            result += std::format(\n                R\"#(\n\t\"{}\",)#\",\n\n                escapeJSONStrings(line));\n        }\n        trimTrailingComma(result);\n        result += \"\\n]\\n\";\n    } else {\n        for (const auto& line : errLines) {\n            result += std::format(\"{}\\n\", line);\n        }\n    }\n    return result;\n}\n\nstatic std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = \"\";\n\n    auto        getModState = [](SP<IKeyboard> keyboard, const char* xkbModName) -> bool {\n        auto IDX = xkb_keymap_mod_get_index(keyboard->m_xkbKeymap, xkbModName);\n\n        if (IDX == XKB_MOD_INVALID)\n            return false;\n\n        return (keyboard->m_modifiersState.locked & (1 << IDX)) > 0;\n    };\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"{\\n\";\n        result += \"\\\"mice\\\": [\\n\";\n\n        for (auto const& m : g_pInputManager->m_pointers) {\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"name\": \"{}\",\n        \"defaultSpeed\": {:.5f},\n        \"scrollFactor\": {:.2f}\n    }},)#\",\n                rc<uintptr_t>(m.get()), escapeJSONStrings(m->m_hlName),\n                m->aq() && m->aq()->getLibinputHandle() ? libinput_device_config_accel_get_default_speed(m->aq()->getLibinputHandle()) : 0.f, m->m_scrollFactor.value_or(-1));\n        }\n\n        trimTrailingComma(result);\n        result += \"\\n],\\n\";\n\n        result += \"\\\"keyboards\\\": [\\n\";\n        for (auto const& k : g_pInputManager->m_keyboards) {\n            const auto INDEX_OPT = k->getActiveLayoutIndex();\n            const auto KI        = INDEX_OPT.has_value() ? std::to_string(INDEX_OPT.value()) : \"none\";\n            const auto KM        = k->getActiveLayout();\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"name\": \"{}\",\n        \"rules\": \"{}\",\n        \"model\": \"{}\",\n        \"layout\": \"{}\",\n        \"variant\": \"{}\",\n        \"options\": \"{}\",\n        \"active_layout_index\": {},\n        \"active_keymap\": \"{}\",\n        \"capsLock\": {},\n        \"numLock\": {},\n        \"main\": {}\n    }},)#\",\n                rc<uintptr_t>(k.get()), escapeJSONStrings(k->m_hlName), escapeJSONStrings(k->m_currentRules.rules), escapeJSONStrings(k->m_currentRules.model),\n                escapeJSONStrings(k->m_currentRules.layout), escapeJSONStrings(k->m_currentRules.variant), escapeJSONStrings(k->m_currentRules.options), KI, escapeJSONStrings(KM),\n                (getModState(k, XKB_MOD_NAME_CAPS) ? \"true\" : \"false\"), (getModState(k, XKB_MOD_NAME_NUM) ? \"true\" : \"false\"), (k->m_active ? \"true\" : \"false\"));\n        }\n\n        trimTrailingComma(result);\n        result += \"\\n],\\n\";\n\n        result += \"\\\"tablets\\\": [\\n\";\n\n        for (auto const& d : g_pInputManager->m_tabletPads) {\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"type\": \"tabletPad\",\n        \"belongsTo\": {{\n            \"address\": \"0x{:x}\",\n            \"name\": \"{}\"\n        }}\n    }},)#\",\n                rc<uintptr_t>(d.get()), rc<uintptr_t>(d->m_parent.get()), escapeJSONStrings(d->m_parent ? d->m_parent->m_hlName : \"\"));\n        }\n\n        for (auto const& d : g_pInputManager->m_tablets) {\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"name\": \"{}\"\n    }},)#\",\n                rc<uintptr_t>(d.get()), escapeJSONStrings(d->m_hlName));\n        }\n\n        for (auto const& d : g_pInputManager->m_tabletTools) {\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"type\": \"tabletTool\"\n    }},)#\",\n                rc<uintptr_t>(d.get()));\n        }\n\n        trimTrailingComma(result);\n        result += \"\\n],\\n\";\n\n        result += \"\\\"touch\\\": [\\n\";\n\n        for (auto const& d : g_pInputManager->m_touches) {\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"name\": \"{}\"\n    }},)#\",\n                rc<uintptr_t>(d.get()), escapeJSONStrings(d->m_hlName));\n        }\n\n        trimTrailingComma(result);\n        result += \"\\n],\\n\";\n\n        result += \"\\\"switches\\\": [\\n\";\n\n        for (auto const& d : g_pInputManager->m_switches) {\n            result += std::format(\n                R\"#(    {{\n        \"address\": \"0x{:x}\",\n        \"name\": \"{}\"\n    }},)#\",\n                rc<uintptr_t>(&d), escapeJSONStrings(d.pDevice ? d.pDevice->getName() : \"\"));\n        }\n\n        trimTrailingComma(result);\n        result += \"\\n]\\n\";\n\n        result += \"}\\n\";\n\n    } else {\n        result += \"mice:\\n\";\n\n        for (auto const& m : g_pInputManager->m_pointers) {\n            result += std::format(\"\\tMouse at {:x}:\\n\\t\\t{}\\n\\t\\t\\tdefault speed: {:.5f}\\n\\t\\t\\tscroll factor: {:.2f}\\n\", rc<uintptr_t>(m.get()), m->m_hlName,\n                                  (m->aq() && m->aq()->getLibinputHandle() ? libinput_device_config_accel_get_default_speed(m->aq()->getLibinputHandle()) : 0.f),\n                                  m->m_scrollFactor.value_or(-1));\n        }\n\n        result += \"\\n\\nKeyboards:\\n\";\n\n        for (auto const& k : g_pInputManager->m_keyboards) {\n            const auto INDEX_OPT = k->getActiveLayoutIndex();\n            const auto KI        = INDEX_OPT.has_value() ? std::to_string(INDEX_OPT.value()) : \"none\";\n            const auto KM        = k->getActiveLayout();\n            result += std::format(\"\\tKeyboard at {:x}:\\n\\t\\t{}\\n\\t\\t\\trules: r \\\"{}\\\", m \\\"{}\\\", l \\\"{}\\\", v \\\"{}\\\", o \\\"{}\\\"\\n\\t\\t\\tactive layout index: {}\\n\\t\\t\\tactive keymap: \"\n                                  \"{}\\n\\t\\t\\tcapsLock: \"\n                                  \"{}\\n\\t\\t\\tnumLock: {}\\n\\t\\t\\tmain: {}\\n\",\n                                  rc<uintptr_t>(k.get()), k->m_hlName, k->m_currentRules.rules, k->m_currentRules.model, k->m_currentRules.layout, k->m_currentRules.variant,\n                                  k->m_currentRules.options, KI, KM, (getModState(k, XKB_MOD_NAME_CAPS) ? \"yes\" : \"no\"), (getModState(k, XKB_MOD_NAME_NUM) ? \"yes\" : \"no\"),\n                                  (k->m_active ? \"yes\" : \"no\"));\n        }\n\n        result += \"\\n\\nTablets:\\n\";\n\n        for (auto const& d : g_pInputManager->m_tabletPads) {\n            result +=\n                std::format(\"\\tTablet Pad at {:x} (belongs to {:x} -> {})\\n\", rc<uintptr_t>(d.get()), rc<uintptr_t>(d->m_parent.get()), d->m_parent ? d->m_parent->m_hlName : \"\");\n        }\n\n        for (auto const& d : g_pInputManager->m_tablets) {\n            result += std::format(\"\\tTablet at {:x}:\\n\\t\\t{}\\n\\t\\t\\tsize: {}x{}mm\\n\", rc<uintptr_t>(d.get()), d->m_hlName, d->aq()->physicalSize.x, d->aq()->physicalSize.y);\n        }\n\n        for (auto const& d : g_pInputManager->m_tabletTools) {\n            result += std::format(\"\\tTablet Tool at {:x}\\n\", rc<uintptr_t>(d.get()));\n        }\n\n        result += \"\\n\\nTouch:\\n\";\n\n        for (auto const& d : g_pInputManager->m_touches) {\n            result += std::format(\"\\tTouch Device at {:x}:\\n\\t\\t{}\\n\", rc<uintptr_t>(d.get()), d->m_hlName);\n        }\n\n        result += \"\\n\\nSwitches:\\n\";\n\n        for (auto const& d : g_pInputManager->m_switches) {\n            result += std::format(\"\\tSwitch Device at {:x}:\\n\\t\\t{}\\n\", rc<uintptr_t>(&d), d.pDevice ? d.pDevice->getName() : \"\");\n        }\n    }\n\n    return result;\n}\n\nstatic std::string animationsRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string ret = \"\";\n    if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {\n        ret += \"animations:\\n\";\n\n        for (auto const& ac : g_pConfigManager->getAnimationConfig()) {\n            ret += std::format(\"\\n\\tname: {}\\n\\t\\toverriden: {}\\n\\t\\tbezier: {}\\n\\t\\tenabled: {}\\n\\t\\tspeed: {:.2f}\\n\\t\\tstyle: {}\\n\", ac.first, sc<int>(ac.second->overridden),\n                               ac.second->internalBezier, ac.second->internalEnabled, ac.second->internalSpeed, ac.second->internalStyle);\n        }\n\n        ret += \"beziers:\\n\";\n\n        for (auto const& bz : g_pAnimationManager->getAllBeziers()) {\n            auto& controlPoints = bz.second->getControlPoints();\n            ret += std::format(\"\\n\\tname: {}\\n\\t\\tX0: {:.2f}\\n\\t\\tY0: {:.2f}\\n\\t\\tX1: {:.2f}\\n\\t\\tY1: {:.2f}\", bz.first, controlPoints[1].x, controlPoints[1].y, controlPoints[2].x,\n                               controlPoints[2].y);\n        }\n    } else {\n        // json\n\n        ret += \"[[\";\n        for (auto const& ac : g_pConfigManager->getAnimationConfig()) {\n            ret += std::format(R\"#(\n{{\n    \"name\": \"{}\",\n    \"overridden\": {},\n    \"bezier\": \"{}\",\n    \"enabled\": {},\n    \"speed\": {:.2f},\n    \"style\": \"{}\"\n}},)#\",\n                               ac.first, ac.second->overridden ? \"true\" : \"false\", escapeJSONStrings(ac.second->internalBezier), ac.second->internalEnabled ? \"true\" : \"false\",\n                               ac.second->internalSpeed, escapeJSONStrings(ac.second->internalStyle));\n        }\n\n        ret[ret.length() - 1] = ']';\n\n        ret += \",\\n[\";\n\n        for (auto const& bz : g_pAnimationManager->getAllBeziers()) {\n            auto& controlPoints = bz.second->getControlPoints();\n            ret += std::format(R\"#(\n{{\n    \"name\": \"{}\",\n    \"X0\": {:.2f},\n    \"Y0\": {:.2f},\n    \"X1\": {:.2f},\n    \"Y1\": {:.2f}\n}},)#\",\n                               escapeJSONStrings(bz.first), controlPoints[1].x, controlPoints[1].y, controlPoints[2].x, controlPoints[2].y);\n        }\n\n        trimTrailingComma(ret);\n\n        ret += \"]]\";\n    }\n\n    return ret;\n}\n\nstatic std::string rollinglogRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = \"\";\n\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\\n\\\"log\\\":\\\"\";\n        result += escapeJSONStrings(Log::logger->rolling());\n        result += \"\\\"]\";\n    } else\n        result = Log::logger->rolling();\n\n    return result;\n}\n\nstatic std::string globalShortcutsRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string ret       = \"\";\n    const auto  SHORTCUTS = PROTO::globalShortcuts->getAllShortcuts();\n    if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {\n        for (auto const& sh : SHORTCUTS) {\n            ret += std::format(\"{}:{} -> {}\\n\", sh.appid, sh.id, sh.description);\n        }\n        if (ret.empty())\n            ret = \"none\";\n    } else {\n        ret += \"[\";\n        for (auto const& sh : SHORTCUTS) {\n            ret += std::format(R\"#(\n{{\n    \"name\": \"{}\",\n    \"description\": \"{}\"\n}},)#\",\n                               escapeJSONStrings(sh.appid + \":\" + sh.id), escapeJSONStrings(sh.description));\n        }\n        trimTrailingComma(ret);\n        ret += \"]\\n\";\n    }\n\n    return ret;\n}\n\nstatic std::string bindsRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string ret = \"\";\n    if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {\n        for (auto const& kb : g_pKeybindManager->m_keybinds) {\n            ret += \"bind\";\n            if (kb->locked)\n                ret += \"l\";\n            if (kb->mouse)\n                ret += \"m\";\n            if (kb->release)\n                ret += \"r\";\n            if (kb->repeat)\n                ret += \"e\";\n            if (kb->nonConsuming)\n                ret += \"n\";\n            if (kb->hasDescription)\n                ret += \"d\";\n\n            ret += std::format(\"\\n\\tmodmask: {}\\n\\tsubmap: {}\\n\\tkey: {}\\n\\tkeycode: {}\\n\\tcatchall: {}\\n\\tdescription: {}\\n\\tdispatcher: {}\\n\\targ: {}\\n\\n\", kb->modmask,\n                               kb->submap.name, kb->key, kb->keycode, kb->catchAll, kb->description, kb->handler, kb->arg);\n        }\n    } else {\n        // json\n        ret += \"[\";\n        for (auto const& kb : g_pKeybindManager->m_keybinds) {\n            ret += std::format(\n                R\"#(\n{{\n    \"locked\": {},\n    \"mouse\": {},\n    \"release\": {},\n    \"repeat\": {},\n    \"longPress\": {},\n    \"non_consuming\": {},\n    \"has_description\": {},\n    \"modmask\": {},\n    \"submap\": \"{}\",\n    \"submap_universal\": \"{}\",\n    \"key\": \"{}\",\n    \"keycode\": {},\n    \"catch_all\": {},\n    \"description\": \"{}\",\n    \"dispatcher\": \"{}\",\n    \"arg\": \"{}\"\n}},)#\",\n                kb->locked ? \"true\" : \"false\", kb->mouse ? \"true\" : \"false\", kb->release ? \"true\" : \"false\", kb->repeat ? \"true\" : \"false\", kb->longPress ? \"true\" : \"false\",\n                kb->nonConsuming ? \"true\" : \"false\", kb->hasDescription ? \"true\" : \"false\", kb->modmask, escapeJSONStrings(kb->submap.name), kb->submapUniversal,\n                escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? \"true\" : \"false\", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler),\n                escapeJSONStrings(kb->arg));\n        }\n        trimTrailingComma(ret);\n        ret += \"]\";\n    }\n\n    return ret;\n}\n\nstd::string versionRequest(eHyprCtlOutputFormat format, std::string request) {\n\n    auto commitMsg = trim(GIT_COMMIT_MESSAGE);\n    std::ranges::replace(commitMsg, '#', ' ');\n\n    if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {\n        std::string result = std::format(\"Hyprland {} built from branch {} at commit {} {} ({}).\\n\"\n                                         \"Date: {}\\n\"\n                                         \"Tag: {}, commits: {}\\n\",\n                                         HYPRLAND_VERSION, GIT_BRANCH, GIT_COMMIT_HASH, GIT_DIRTY, commitMsg, GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS);\n\n        result += \"\\n\";\n        result += getBuiltSystemLibraryNames();\n        result += \"\\n\";\n        result += \"Version ABI string: \";\n        result += __hyprland_api_get_hash();\n        result += \"\\n\";\n\n#if (!ISDEBUG && !defined(NO_XWAYLAND) && !defined(BUILT_WITH_NIX))\n        result += \"no flags were set\\n\";\n#else\n        result += \"flags set:\\n\";\n#if ISDEBUG\n        result += \"debug\\n\";\n#endif\n#ifdef NO_XWAYLAND\n        result += \"no xwayland\\n\";\n#endif\n#ifdef BUILT_WITH_NIX\n        result += \"nix\\n\";\n#endif\n#endif\n        return result;\n    } else {\n        std::string result = std::format(\n            R\"#({{\n    \"branch\": \"{}\",\n    \"commit\": \"{}\",\n    \"version\": \"{}\",\n    \"dirty\": {},\n    \"commit_message\": \"{}\",\n    \"commit_date\": \"{}\",\n    \"tag\": \"{}\",\n    \"commits\": \"{}\",\n    \"buildAquamarine\": \"{}\",\n    \"buildHyprlang\": \"{}\",\n    \"buildHyprutils\": \"{}\",\n    \"buildHyprcursor\": \"{}\",\n    \"buildHyprgraphics\": \"{}\",\n    \"systemAquamarine\": \"{}\",\n    \"systemHyprlang\": \"{}\",\n    \"systemHyprutils\": \"{}\",\n    \"systemHyprcursor\": \"{}\",\n    \"systemHyprgraphics\": \"{}\",\n    \"abiHash\": \"{}\",\n    \"flags\": [)#\",\n            GIT_BRANCH, GIT_COMMIT_HASH, HYPRLAND_VERSION, (strcmp(GIT_DIRTY, \"dirty\") == 0 ? \"true\" : \"false\"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG,\n            GIT_COMMITS, AQUAMARINE_VERSION, HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION, getSystemLibraryVersion(\"aquamarine\"),\n            getSystemLibraryVersion(\"hyprlang\"), getSystemLibraryVersion(\"hyprutils\"), getSystemLibraryVersion(\"hyprcursor\"), getSystemLibraryVersion(\"hyprgraphics\"),\n            __hyprland_api_get_hash());\n\n#if ISDEBUG\n        result += \"\\\"debug\\\",\";\n#endif\n#ifdef NO_XWAYLAND\n        result += \"\\\"no xwayland\\\",\";\n#endif\n#ifdef BUILT_WITH_NIX\n        result += \"\\\"nix\\\",\";\n#endif\n\n        trimTrailingComma(result);\n\n        result += \"]\\n}\";\n\n        return result;\n    }\n\n    return \"\"; // make the compiler happy\n}\n\nstd::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string result = versionRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, \"\");\n\n    static auto check   = [](bool y) -> std::string { return y ? \"✔️\" : \"❌\"; };\n    static auto backend = [](Aquamarine::eBackendType t) -> std::string {\n        switch (t) {\n            case Aquamarine::AQ_BACKEND_DRM: return \"drm\";\n            case Aquamarine::AQ_BACKEND_HEADLESS: return \"headless\";\n            case Aquamarine::AQ_BACKEND_WAYLAND: return \"wayland\";\n            default: break;\n        }\n        return \"?\";\n    };\n\n    result += \"\\n\\nSystem Information:\\n\";\n\n    struct utsname unameInfo;\n\n    uname(&unameInfo);\n\n    result += \"System name: \" + std::string{unameInfo.sysname} + \"\\n\";\n    result += \"Node name: \" + std::string{unameInfo.nodename} + \"\\n\";\n    result += \"Release: \" + std::string{unameInfo.release} + \"\\n\";\n    result += \"Version: \" + std::string{unameInfo.version} + \"\\n\";\n    result += \"\\n\";\n    result += getBuiltSystemLibraryNames();\n    result += \"\\n\";\n\n    result += \"\\n\\n\";\n\n#if defined(__DragonFly__) || defined(__FreeBSD__)\n    const std::string GPUINFO = execAndGet(\"pciconf -lv | grep -F -A4 vga\");\n#elif defined(__arm__) || defined(__aarch64__)\n    std::string                 GPUINFO;\n    const std::filesystem::path dev_tree = \"/proc/device-tree\";\n    try {\n        if (std::filesystem::exists(dev_tree) && std::filesystem::is_directory(dev_tree)) {\n            std::for_each(std::filesystem::directory_iterator(dev_tree), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& entry) {\n                if (std::filesystem::is_directory(entry) && entry.path().filename().string().starts_with(\"soc\")) {\n                    std::for_each(std::filesystem::directory_iterator(entry.path()), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& sub_entry) {\n                        if (std::filesystem::is_directory(sub_entry) && sub_entry.path().filename().string().starts_with(\"gpu\")) {\n                            std::filesystem::path file_path = sub_entry.path() / \"compatible\";\n                            std::ifstream         file(file_path);\n                            if (file)\n                                GPUINFO.append(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());\n                        }\n                    });\n                }\n            });\n        }\n    } catch (...) { GPUINFO = \"error\"; }\n#else\n    const std::string GPUINFO = execAndGet(\"lspci -vnn | grep -E '(VGA|Display|3D)'\");\n#endif\n    result += \"GPU information: \\n\" + GPUINFO;\n    if (GPUINFO.contains(\"NVIDIA\") && std::filesystem::exists(\"/proc/driver/nvidia/version\")) {\n        std::ifstream file(\"/proc/driver/nvidia/version\");\n        std::string   line;\n        if (file.is_open()) {\n            while (std::getline(file, line)) {\n                if (!line.contains(\"NVRM\"))\n                    continue;\n                result += line;\n                result += \"\\n\";\n            }\n        } else\n            result += \"error\";\n    }\n    result += \"\\n\\n\";\n\n    if (std::ifstream file(\"/etc/os-release\"); file.is_open()) {\n        std::stringstream buffer;\n        buffer << file.rdbuf();\n        result += \"os-release: \" + buffer.str() + \"\\n\\n\";\n    } else\n        result += \"os-release: error\\n\\n\";\n\n    result += \"plugins:\\n\";\n    if (g_pPluginSystem) {\n        for (auto const& pl : g_pPluginSystem->getAllPlugins()) {\n            result += std::format(\"  {} by {} ver {}\\n\", pl->m_name, pl->m_author, pl->m_version);\n        }\n    } else\n        result += \"\\tunknown: not runtime\\n\";\n\n    if (g_pHyprOpenGL) {\n        result += std::format(\"\\nExplicit sync: {}\", g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext ? \"supported\" : \"missing\");\n        result += std::format(\"\\nGL ver: {}\", g_pHyprOpenGL->m_eglContextVersion == CHyprOpenGLImpl::EGL_CONTEXT_GLES_3_2 ? \"3.2\" : \"3.0\");\n    }\n\n    if (g_pCompositor) {\n        result += std::format(\"\\nBackend: {}\", g_pCompositor->m_aqBackend->hasSession() ? \"drm\" : \"sessionless\");\n\n        result += \"\\n\\nMonitor info:\";\n\n        for (const auto& m : g_pCompositor->m_monitors) {\n            result += std::format(\"\\n\\tPanel {}: {}x{}, {} {} {} {} -> backend {}\\n\\t\\texplicit {}\\n\\t\\tedid:\\n\\t\\t\\thdr {}\\n\\t\\t\\tchroma {}\\n\\t\\t\\tbt2020 {}\\n\\t\\tvrr capable \"\n                                  \"{}\\n\\t\\tnon-desktop {}\\n\\t\\t\",\n                                  m->m_name, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_output->name, m->m_output->make, m->m_output->model, m->m_output->serial,\n                                  backend(m->m_output->getBackend()->type()), check(m->m_output->supportsExplicit), check(m->m_output->parsedEDID.hdrMetadata.has_value()),\n                                  check(m->m_output->parsedEDID.chromaticityCoords.has_value()), check(m->m_output->parsedEDID.supportsBT2020), check(m->m_output->vrrCapable),\n                                  check(m->m_output->nonDesktop));\n        }\n    }\n\n    if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.sysInfoConfig) {\n        result += \"\\n======Config-Start======\\n\";\n        result += g_pConfigManager->getConfigString();\n        result += \"\\n======Config-End========\\n\";\n    }\n\n    return result;\n}\n\nstatic std::string dispatchRequest(eHyprCtlOutputFormat format, std::string in) {\n    // get rid of the dispatch keyword\n    in = in.substr(in.find_first_of(' ') + 1);\n\n    const auto DISPATCHSTR = in.substr(0, in.find_first_of(' '));\n\n    auto       DISPATCHARG = std::string();\n    if (sc<int>(in.find_first_of(' ')) != -1)\n        DISPATCHARG = in.substr(in.find_first_of(' ') + 1);\n\n    const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(DISPATCHSTR);\n    if (DISPATCHER == g_pKeybindManager->m_dispatchers.end())\n        return \"Invalid dispatcher\";\n\n    SDispatchResult res = DISPATCHER->second(DISPATCHARG);\n\n    Log::logger->log(Log::DEBUG, \"Hyprctl: dispatcher {} : {}{}\", DISPATCHSTR, DISPATCHARG, res.success ? \"\" : \" -> \" + res.error);\n\n    return res.success ? \"ok\" : res.error;\n}\n\nstatic std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) {\n    // Find the first space to strip the keyword keyword\n    auto const firstSpacePos = in.find_first_of(' ');\n    if (firstSpacePos == std::string::npos) // Handle the case where there's no space found (invalid input)\n        return \"Invalid input: no space found\";\n\n    // Strip the keyword\n    in = in.substr(firstSpacePos + 1);\n\n    // Find the next space for the COMMAND and VALUE\n    auto const secondSpacePos = in.find_first_of(' ');\n    if (secondSpacePos == std::string::npos) // Handle the case where there's no second space (invalid input)\n        return \"Invalid input: command and value not properly formatted\";\n\n    // Extract COMMAND and VALUE\n    const auto COMMAND = in.substr(0, secondSpacePos);\n    const auto VALUE   = in.substr(secondSpacePos + 1);\n\n    // If COMMAND is empty, handle accordingly\n    if (COMMAND.empty())\n        return \"Invalid input: command is empty\";\n\n    g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = true;\n\n    std::string retval = g_pConfigManager->parseKeyword(COMMAND, VALUE);\n\n    g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false;\n\n    if (COMMAND == \"source\") {\n        g_pConfigManager->m_wantsMonitorReload = true;\n        g_pEventLoopManager->doLater([] { g_pConfigManager->reloadRules(); });\n    }\n\n    // if we are executing a dynamic source we have to reload everything, so every if will have a check for source.\n    if (COMMAND == \"monitor\")\n        g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords\n\n    if (COMMAND.contains(\"monitorv2\"))\n        g_pEventLoopManager->doLater([] { g_pConfigManager->m_wantsMonitorReload = true; });\n\n    if (COMMAND.contains(\"input\") || COMMAND.contains(\"device\") || COMMAND == \"source\") {\n        g_pInputManager->setKeyboardLayout();     // update kb layout\n        g_pInputManager->setPointerConfigs();     // update mouse cfgs\n        g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs\n        g_pInputManager->setTabletConfigs();      // update tablets\n    }\n\n    if (COMMAND.contains(\"general:layout\") || (COMMAND.contains(\"workspace\") && VALUE.contains(\"layout:\")))\n        Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();\n\n    if (COMMAND.contains(\"decoration:screen_shader\") || COMMAND == \"source\")\n        g_pHyprRenderer->m_reloadScreenShader = true;\n\n    if (COMMAND.contains(\"blur\") || COMMAND == \"source\") {\n        for (auto const& m : g_pCompositor->m_monitors) {\n            if (m)\n                m->m_blurFBDirty = true;\n        }\n    }\n\n    if (COMMAND.contains(\"misc:disable_autoreload\"))\n        g_pConfigManager->updateWatcher();\n\n    // decorations will probably need a repaint\n    if (COMMAND.contains(\"decoration:\") || COMMAND.contains(\"border\") || COMMAND == \"workspace\" || COMMAND.contains(\"zoom_factor\") || COMMAND == \"source\") {\n        static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>(\"cursor:zoom_factor\");\n        for (auto const& m : g_pCompositor->m_monitors) {\n            *(m->m_cursorZoom) = *PZOOMFACTOR;\n            g_pHyprRenderer->damageMonitor(m);\n            if (m->m_activeWorkspace)\n                m->m_activeWorkspace->m_space->recalculate();\n        }\n    }\n\n    if (COMMAND.contains(\"windowrule \") || COMMAND.contains(\"windowrule[\"))\n        g_pConfigManager->reloadRules();\n\n    if (COMMAND.contains(\"layerrule\") || COMMAND.contains(\"layerrule[\")) {\n        g_pConfigManager->reloadRules();\n        // Damage all monitors to redraw static layers.\n        for (auto const& m : g_pCompositor->m_monitors) {\n            g_pHyprRenderer->damageMonitor(m);\n        }\n    }\n\n    if (COMMAND.contains(\"workspace\"))\n        g_pConfigManager->ensurePersistentWorkspacesPresent();\n\n    Log::logger->log(Log::DEBUG, \"Hyprctl: keyword {} : {}\", COMMAND, VALUE);\n\n    if (retval.empty())\n        return \"ok\";\n\n    return retval;\n}\n\nstatic std::string reloadRequest(eHyprCtlOutputFormat format, std::string request) {\n\n    const auto REQMODE = request.substr(request.find_last_of(' ') + 1);\n\n    if (REQMODE == \"config-only\")\n        g_pConfigManager->m_noMonitorReload = true;\n\n    g_pConfigManager->reload();\n\n    return \"ok\";\n}\n\nstatic std::string killRequest(eHyprCtlOutputFormat format, std::string request) {\n    g_pInputManager->setClickMode(CLICKMODE_KILL);\n\n    return \"ok\";\n}\n\nstatic std::string splashRequest(eHyprCtlOutputFormat format, std::string request) {\n    return g_pCompositor->m_currentSplash;\n}\n\nstatic std::string cursorPosRequest(eHyprCtlOutputFormat format, std::string request) {\n    const auto CURSORPOS = g_pInputManager->getMouseCoordsInternal().floor();\n\n    if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {\n        return std::format(\"{}, {}\", sc<int>(CURSORPOS.x), sc<int>(CURSORPOS.y));\n    } else {\n        return std::format(R\"#(\n{{\n    \"x\": {},\n    \"y\": {}\n}}\n)#\",\n                           sc<int>(CURSORPOS.x), sc<int>(CURSORPOS.y));\n    }\n\n    return \"error\";\n}\n\nstatic std::string dispatchBatch(eHyprCtlOutputFormat format, std::string request) {\n    // split by ; ignores ; inside [] and adds ; on last command\n\n    request                     = request.substr(9);\n    std::string       reply     = \"\";\n    const std::string DELIMITER = \"\\n\\n\\n\";\n    int               bracket   = 0;\n    size_t            idx       = 0;\n\n    for (size_t i = 0; i <= request.size(); ++i) {\n        char ch = (i < request.size()) ? request[i] : ';';\n        if (ch == '[')\n            ++bracket;\n        else if (ch == ']')\n            --bracket;\n        else if (ch == ';' && bracket == 0) {\n            if (idx < i)\n                reply += g_pHyprCtl->getReply(trim(request.substr(idx, i - idx))).append(DELIMITER);\n            idx = i + 1;\n            continue;\n        }\n    }\n\n    return reply.substr(0, std::max(sc<int>(reply.size() - DELIMITER.size()), 0));\n}\n\nstatic std::string dispatchSetCursor(eHyprCtlOutputFormat format, std::string request) {\n    CVarList    vars(request, 0, ' ');\n\n    const auto  SIZESTR = vars[vars.size() - 1];\n    std::string theme   = \"\";\n    for (size_t i = 1; i < vars.size() - 1; ++i)\n        theme += vars[i] + \" \";\n    if (!theme.empty())\n        theme.pop_back();\n\n    int size = 0;\n    try {\n        size = std::stoi(SIZESTR);\n    } catch (...) { return \"size not int\"; }\n\n    if (size <= 0)\n        return \"size not positive\";\n\n    if (!g_pCursorManager->changeTheme(theme, size))\n        return \"failed to set cursor\";\n\n    return \"ok\";\n}\n\nstatic std::string switchXKBLayoutRequest(eHyprCtlOutputFormat format, std::string request) {\n    CVarList      vars(request, 0, ' ');\n\n    const auto    KB  = vars[1];\n    const auto    CMD = vars[2];\n\n    SP<IKeyboard> pKeyboard;\n\n    auto          updateKeyboard = [](const SP<IKeyboard> KEEB, const std::string& CMD) -> std::optional<std::string> {\n        const auto         LAYOUTS      = xkb_keymap_num_layouts(KEEB->m_xkbKeymap);\n        xkb_layout_index_t activeLayout = 0;\n        while (activeLayout < LAYOUTS) {\n            if (xkb_state_layout_index_is_active(KEEB->m_xkbState, activeLayout, XKB_STATE_LAYOUT_EFFECTIVE) == 1)\n                break;\n\n            activeLayout++;\n        }\n\n        if (CMD == \"next\")\n            KEEB->updateModifiers(KEEB->m_modifiersState.depressed, KEEB->m_modifiersState.latched, KEEB->m_modifiersState.locked, activeLayout > LAYOUTS ? 0 : activeLayout + 1);\n        else if (CMD == \"prev\")\n            KEEB->updateModifiers(KEEB->m_modifiersState.depressed, KEEB->m_modifiersState.latched, KEEB->m_modifiersState.locked,\n                                  activeLayout == 0 ? LAYOUTS - 1 : activeLayout - 1);\n        else {\n            int requestedLayout = 0;\n            try {\n                requestedLayout = std::stoi(CMD);\n            } catch (std::exception& e) { return \"invalid arg 2\"; }\n\n            if (requestedLayout < 0 || sc<uint64_t>(requestedLayout) > LAYOUTS - 1) {\n                return \"layout idx out of range of \" + std::to_string(LAYOUTS);\n            }\n\n            KEEB->updateModifiers(KEEB->m_modifiersState.depressed, KEEB->m_modifiersState.latched, KEEB->m_modifiersState.locked, requestedLayout);\n        }\n\n        return std::nullopt;\n    };\n\n    if (KB == \"main\" || KB == \"active\" || KB == \"current\") {\n        for (auto const& k : g_pInputManager->m_keyboards) {\n            if (!k->m_active)\n                continue;\n\n            pKeyboard = k;\n            break;\n        }\n    } else if (KB == \"all\") {\n        std::string result = \"\";\n        for (auto const& k : g_pInputManager->m_keyboards) {\n            auto res = updateKeyboard(k, CMD);\n            if (res.has_value())\n                result += *res + \"\\n\";\n        }\n        return result.empty() ? \"ok\" : result;\n    } else {\n        auto k = std::ranges::find_if(g_pInputManager->m_keyboards, [&](const auto& other) { return other->m_hlName == deviceNameToInternalString(KB); });\n\n        if (k == g_pInputManager->m_keyboards.end())\n            return \"device not found\";\n\n        pKeyboard = *k;\n    }\n\n    if (!pKeyboard)\n        return \"no device\";\n\n    auto result = updateKeyboard(pKeyboard, CMD);\n\n    if (result.has_value())\n        return *result;\n\n    return \"ok\";\n}\n\nstatic std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string request) {\n    CVarList    vars(request, 0, ' ');\n\n    std::string errorMessage = \"\";\n\n    if (vars.size() < 3) {\n        g_pHyprError->destroy();\n\n        if (vars.size() == 2 && !vars[1].contains(\"dis\"))\n            return \"var 1 not color or disable\";\n\n        return \"ok\";\n    }\n\n    const CHyprColor COLOR = configStringToInt(vars[1]).value_or(0);\n\n    for (size_t i = 2; i < vars.size(); ++i)\n        errorMessage += vars[i] + ' ';\n\n    if (errorMessage.empty()) {\n        g_pHyprError->destroy();\n    } else {\n        errorMessage.pop_back(); // pop last space\n        g_pHyprError->queueCreate(errorMessage, COLOR);\n    }\n\n    return \"ok\";\n}\n\nstatic std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n\n    if (vars.size() < 3)\n        return \"not enough args\";\n\n    const auto WINREGEX = vars[1];\n    const auto PROP     = vars[2];\n\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(WINREGEX);\n\n    if (!PWINDOW)\n        return \"window not found\";\n\n    const bool FORMNORM = format == FORMAT_NORMAL;\n\n    auto       sizeToString = [&](bool max) -> std::string {\n        auto sizeValue = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE));\n        if (max)\n            sizeValue = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D(INFINITY, INFINITY));\n\n        if (FORMNORM)\n            return std::format(\"{} {}\", sizeValue.x, sizeValue.y);\n        else {\n            std::string xSizeString = (sizeValue.x != INFINITY) ? std::to_string(sizeValue.x) : \"null\";\n            std::string ySizeString = (sizeValue.y != INFINITY) ? std::to_string(sizeValue.y) : \"null\";\n            return std::format(R\"({{\"{}\": [{},{}]}})\", PROP, xSizeString, ySizeString);\n        }\n    };\n\n    auto alphaToString = [&](Desktop::Types::COverridableVar<Desktop::Types::SAlphaValue>& alpha, bool getAlpha) -> std::string {\n        if (FORMNORM) {\n            if (getAlpha)\n                return std::format(\"{}\", alpha.valueOrDefault().alpha);\n            else\n                return std::format(\"{}\", alpha.valueOrDefault().overridden);\n        } else {\n            if (getAlpha)\n                return std::format(R\"({{\"{}\": {}}})\", PROP, alpha.valueOrDefault().alpha);\n            else\n                return std::format(R\"({{\"{}\": {}}})\", PROP, alpha.valueOrDefault().overridden);\n        }\n    };\n\n    auto borderColorToString = [&](bool active) -> std::string {\n        static auto PACTIVECOL              = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.active_border\");\n        static auto PINACTIVECOL            = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.inactive_border\");\n        static auto PNOGROUPACTIVECOL       = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.nogroup_border_active\");\n        static auto PNOGROUPINACTIVECOL     = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.nogroup_border\");\n        static auto PGROUPACTIVECOL         = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_active\");\n        static auto PGROUPINACTIVECOL       = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_inactive\");\n        static auto PGROUPACTIVELOCKEDCOL   = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_locked_active\");\n        static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_locked_inactive\");\n\n        const bool  GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false;\n\n        if (active) {\n            auto* const       ACTIVECOL            = (CGradientValueData*)(PACTIVECOL.ptr())->getData();\n            auto* const       NOGROUPACTIVECOL     = (CGradientValueData*)(PNOGROUPACTIVECOL.ptr())->getData();\n            auto* const       GROUPACTIVECOL       = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData();\n            auto* const       GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData();\n            const auto* const ACTIVECOLOR =\n                !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);\n\n            std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString();\n            if (FORMNORM)\n                return borderColorString;\n            else\n                return std::format(R\"({{\"{}\": \"{}\"}})\", PROP, borderColorString);\n        } else {\n            auto* const       INACTIVECOL            = (CGradientValueData*)(PINACTIVECOL.ptr())->getData();\n            auto* const       NOGROUPINACTIVECOL     = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData();\n            auto* const       GROUPINACTIVECOL       = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData();\n            auto* const       GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData();\n            const auto* const INACTIVECOLOR          = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) :\n                                                                           (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);\n\n            std::string       borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString();\n            if (FORMNORM)\n                return borderColorString;\n            else\n                return std::format(R\"({{\"{}\": \"{}\"}})\", PROP, borderColorString);\n        }\n    };\n\n    auto windowPropToString = [&](auto& prop) -> std::string {\n        if (FORMNORM)\n            return std::format(\"{}\", prop.valueOrDefault());\n        else\n            return std::format(R\"({{\"{}\": {}}})\", PROP, prop.valueOrDefault());\n    };\n\n    if (PROP == \"animation\") {\n        auto& animationStyle = PWINDOW->m_ruleApplicator->animationStyle();\n        if (FORMNORM)\n            return animationStyle.valueOr(\"(unset)\");\n        else\n            return std::format(R\"({{\"{}\": \"{}\"}})\", PROP, animationStyle.valueOr(\"\"));\n    } else if (PROP == \"max_size\")\n        return sizeToString(true);\n    else if (PROP == \"min_size\")\n        return sizeToString(false);\n    else if (PROP == \"opacity\")\n        return alphaToString(PWINDOW->m_ruleApplicator->alpha(), true);\n    else if (PROP == \"opacity_inactive\")\n        return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), true);\n    else if (PROP == \"opacity_fullscreen\")\n        return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), true);\n    else if (PROP == \"opacity_override\")\n        return alphaToString(PWINDOW->m_ruleApplicator->alpha(), false);\n    else if (PROP == \"opacity_inactive_override\")\n        return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), false);\n    else if (PROP == \"opacity_fullscreen_override\")\n        return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), false);\n    else if (PROP == \"active_border_color\")\n        return borderColorToString(true);\n    else if (PROP == \"inactive_border_color\")\n        return borderColorToString(false);\n    else if (PROP == \"allows_input\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->allowsInput());\n    else if (PROP == \"decorate\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->decorate());\n    else if (PROP == \"focus_on_activate\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->focusOnActivate());\n    else if (PROP == \"keep_aspect_ratio\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->keepAspectRatio());\n    else if (PROP == \"nearest_neighbor\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->nearestNeighbor());\n    else if (PROP == \"no_anim\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noAnim());\n    else if (PROP == \"no_blur\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noBlur());\n    else if (PROP == \"no_dim\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noDim());\n    else if (PROP == \"no_focus\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noFocus());\n    else if (PROP == \"no_max_size\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noMaxSize());\n    else if (PROP == \"no_shadow\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noShadow());\n    else if (PROP == \"no_shortcuts_inhibit\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noShortcutsInhibit());\n    else if (PROP == \"opaque\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->opaque());\n    else if (PROP == \"dim_around\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->dimAround());\n    else if (PROP == \"force_rgbx\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->RGBX());\n    else if (PROP == \"sync_fullscreen\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->syncFullscreen());\n    else if (PROP == \"immediate\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->tearing());\n    else if (PROP == \"xray\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->xray());\n    else if (PROP == \"render_unfocused\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->renderUnfocused());\n    else if (PROP == \"no_follow_mouse\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noFollowMouse());\n    else if (PROP == \"no_screen_share\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noScreenShare());\n    else if (PROP == \"no_vrr\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->noVRR());\n    else if (PROP == \"persistent_size\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->persistentSize());\n    else if (PROP == \"stay_focused\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->stayFocused());\n    else if (PROP == \"idle_inhibit\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->idleInhibitMode());\n    else if (PROP == \"border_size\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->borderSize());\n    else if (PROP == \"rounding\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->rounding());\n    else if (PROP == \"rounding_power\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->roundingPower());\n    else if (PROP == \"scroll_mouse\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->scrollMouse());\n    else if (PROP == \"scroll_touchpad\")\n        return windowPropToString(PWINDOW->m_ruleApplicator->scrollTouchpad());\n\n    return \"prop not found\";\n}\n\nstatic std::string dispatchGetOption(eHyprCtlOutputFormat format, std::string request) {\n    std::string curitem = \"\";\n\n    auto        nextItem = [&]() {\n        auto idx = request.find_first_of(' ');\n\n        if (idx != std::string::npos) {\n            curitem = request.substr(0, idx);\n            request = request.substr(idx + 1);\n        } else {\n            curitem = request;\n            request = \"\";\n        }\n\n        curitem = trim(curitem);\n    };\n\n    nextItem();\n    nextItem();\n\n    const auto VAR = g_pConfigManager->getHyprlangConfigValuePtr(curitem);\n\n    if (!VAR)\n        return \"no such option\";\n\n    const auto VAL  = VAR->getValue();\n    const auto TYPE = std::type_index(VAL.type());\n\n    if (format == FORMAT_NORMAL) {\n        if (TYPE == typeid(Hyprlang::INT))\n            return std::format(\"int: {}\\nset: {}\", std::any_cast<Hyprlang::INT>(VAL), VAR->m_bSetByUser);\n        else if (TYPE == typeid(Hyprlang::FLOAT))\n            return std::format(\"float: {:2f}\\nset: {}\", std::any_cast<Hyprlang::FLOAT>(VAL), VAR->m_bSetByUser);\n        else if (TYPE == typeid(Hyprlang::VEC2))\n            return std::format(\"vec2: [{}, {}]\\nset: {}\", std::any_cast<Hyprlang::VEC2>(VAL).x, std::any_cast<Hyprlang::VEC2>(VAL).y, VAR->m_bSetByUser);\n        else if (TYPE == typeid(Hyprlang::STRING))\n            return std::format(\"str: {}\\nset: {}\", std::any_cast<Hyprlang::STRING>(VAL), VAR->m_bSetByUser);\n        else if (TYPE == typeid(void*))\n            return std::format(\"custom type: {}\\nset: {}\", sc<ICustomConfigValueData*>(std::any_cast<void*>(VAL))->toString(), VAR->m_bSetByUser);\n    } else {\n        if (TYPE == typeid(Hyprlang::INT))\n            return std::format(R\"({{\"option\": \"{}\", \"int\": {}, \"set\": {} }})\", curitem, std::any_cast<Hyprlang::INT>(VAL), VAR->m_bSetByUser);\n        else if (TYPE == typeid(Hyprlang::FLOAT))\n            return std::format(R\"({{\"option\": \"{}\", \"float\": {:2f}, \"set\": {} }})\", curitem, std::any_cast<Hyprlang::FLOAT>(VAL), VAR->m_bSetByUser);\n        else if (TYPE == typeid(Hyprlang::VEC2))\n            return std::format(R\"({{\"option\": \"{}\", \"vec2\": [{},{}], \"set\": {} }})\", curitem, std::any_cast<Hyprlang::VEC2>(VAL).x, std::any_cast<Hyprlang::VEC2>(VAL).y,\n                               VAR->m_bSetByUser);\n        else if (TYPE == typeid(Hyprlang::STRING))\n            return std::format(R\"({{\"option\": \"{}\", \"str\": \"{}\", \"set\": {} }})\", curitem, escapeJSONStrings(std::any_cast<Hyprlang::STRING>(VAL)), VAR->m_bSetByUser);\n        else if (TYPE == typeid(void*))\n            return std::format(R\"({{\"option\": \"{}\", \"custom\": \"{}\", \"set\": {} }})\", curitem, sc<ICustomConfigValueData*>(std::any_cast<void*>(VAL))->toString(), VAR->m_bSetByUser);\n    }\n\n    return \"invalid type (internal error)\";\n}\n\nstatic std::string decorationRequest(eHyprCtlOutputFormat format, std::string request) {\n    CVarList   vars(request, 0, ' ');\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(vars[1]);\n\n    if (!PWINDOW)\n        return \"none\";\n\n    std::string result = \"\";\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n        result += \"[\";\n        for (auto const& wd : PWINDOW->m_windowDecorations) {\n            result += \"{\\n\\\"decorationName\\\": \\\"\" + wd->getDisplayName() + \"\\\",\\n\\\"priority\\\": \" + std::to_string(wd->getPositioningInfo().priority) + \"\\n},\";\n        }\n\n        trimTrailingComma(result);\n        result += \"]\";\n    } else {\n        result = +\"Decoration\\tPriority\\n\";\n        for (auto const& wd : PWINDOW->m_windowDecorations) {\n            result += wd->getDisplayName() + \"\\t\" + std::to_string(wd->getPositioningInfo().priority) + \"\\n\";\n        }\n    }\n\n    return result;\n}\n\nstatic std::string dispatchOutput(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n\n    if (vars.size() < 2)\n        return \"not enough args\";\n\n    const auto MODE = vars[1];\n\n    bool       added = false;\n\n    if (!vars[3].empty()) {\n        for (auto const& m : g_pCompositor->m_realMonitors) {\n            if (m->m_name == vars[3])\n                return \"Name already taken\";\n        }\n    }\n\n    if (MODE == \"create\" || MODE == \"add\") {\n        if (g_pCompositor->getMonitorFromName(vars[3]))\n            return \"A real monitor already uses that name.\";\n\n        for (auto const& impl : g_pCompositor->m_aqBackend->getImplementations() | std::views::reverse) {\n            auto type = impl->type();\n\n            if (type == Aquamarine::AQ_BACKEND_HEADLESS && (vars[2] == \"headless\" || vars[2] == \"auto\")) {\n                added = true;\n                impl->createOutput(vars[3]);\n                break;\n            }\n\n            if (type == Aquamarine::AQ_BACKEND_WAYLAND && (vars[2] == \"wayland\" || vars[2] == \"auto\")) {\n                added = true;\n                impl->createOutput(vars[3]);\n                break;\n            }\n        }\n\n        if (!added)\n            return \"no backend replied to the request\";\n\n    } else if (MODE == \"destroy\" || MODE == \"remove\") {\n        const auto PMONITOR = g_pCompositor->getMonitorFromName(vars[2]);\n\n        if (!PMONITOR)\n            return \"output not found\";\n\n        if (!PMONITOR->m_createdByUser)\n            return \"cannot remove a real display. Use the monitor keyword.\";\n\n        PMONITOR->m_output->destroy();\n    }\n\n    return \"ok\";\n}\n\nstatic std::string dispatchPlugin(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n\n    if (vars.size() < 2)\n        return \"not enough args\";\n\n    const auto OPERATION = vars[1];\n    const auto PATH      = vars[2];\n\n    if (OPERATION == \"load\") {\n        if (vars.size() < 3)\n            return \"not enough args\";\n\n        g_pHyprCtl->m_currentRequestParams.pendingPromise = CPromise<std::string>::make([PATH](SP<CPromiseResolver<std::string>> resolver) {\n            g_pPluginSystem->loadPlugin(PATH)->then([resolver, PATH](SP<CPromiseResult<CPlugin*>> result) {\n                if (result->hasError()) {\n                    resolver->reject(result->error());\n                    return;\n                }\n\n                resolver->resolve(\"ok\");\n            });\n        });\n\n        return \"ok\";\n    } else if (OPERATION == \"unload\") {\n        if (vars.size() < 3)\n            return \"not enough args\";\n\n        const auto PLUGIN = g_pPluginSystem->getPluginByPath(PATH);\n\n        if (!PLUGIN)\n            return \"plugin not loaded\";\n\n        g_pPluginSystem->unloadPlugin(PLUGIN);\n    } else if (OPERATION == \"list\") {\n        const auto  PLUGINS = g_pPluginSystem->getAllPlugins();\n        std::string result  = \"\";\n\n        if (format == eHyprCtlOutputFormat::FORMAT_JSON) {\n            result += \"[\";\n\n            if (PLUGINS.empty())\n                return \"[]\";\n\n            for (auto const& p : PLUGINS) {\n                result += std::format(\n                    R\"#(\n{{\n    \"name\": \"{}\",\n    \"author\": \"{}\",\n    \"handle\": \"{:x}\",\n    \"version\": \"{}\",\n    \"description\": \"{}\"\n}},)#\",\n                    escapeJSONStrings(p->m_name), escapeJSONStrings(p->m_author), rc<uintptr_t>(p->m_handle), escapeJSONStrings(p->m_version), escapeJSONStrings(p->m_description));\n            }\n            trimTrailingComma(result);\n            result += \"]\";\n        } else {\n            if (PLUGINS.empty())\n                return \"no plugins loaded\";\n\n            for (auto const& p : PLUGINS) {\n                result += std::format(\"\\nPlugin {} by {}:\\n\\tHandle: {:x}\\n\\tVersion: {}\\n\\tDescription: {}\\n\", p->m_name, p->m_author, rc<uintptr_t>(p->m_handle), p->m_version,\n                                      p->m_description);\n            }\n        }\n\n        return result;\n    } else {\n        return \"unknown opt\";\n    }\n\n    return \"ok\";\n}\n\nstatic std::string dispatchNotify(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n\n    if (vars.size() < 5)\n        return \"not enough args\";\n\n    const auto ICON = vars[1];\n\n    if (!isNumber(ICON))\n        return \"invalid arg 1\";\n\n    int icon = -1;\n    try {\n        icon = std::stoi(ICON);\n    } catch (std::exception& e) { return \"invalid arg 1\"; }\n\n    if (icon > ICON_NONE || icon < 0)\n        icon = ICON_NONE;\n\n    const auto TIME = vars[2];\n    int        time = 0;\n    try {\n        time = std::stoi(TIME);\n    } catch (std::exception& e) { return \"invalid arg 2\"; }\n\n    const auto COLOR_RESULT = configStringToInt(vars[3]);\n    if (!COLOR_RESULT)\n        return \"invalid arg 3\";\n    CHyprColor color = *COLOR_RESULT;\n\n    size_t     msgidx   = 4;\n    float      fontsize = 13.f;\n    if (vars[msgidx].length() > 9 && vars[msgidx].compare(0, 9, \"fontsize:\") == 0) {\n        const auto FONTSIZE = vars[msgidx].substr(9);\n\n        if (!isNumber(FONTSIZE, true))\n            return \"invalid fontsize kwarg\";\n\n        try {\n            fontsize = std::stoi(FONTSIZE);\n        } catch (std::exception& e) { return \"invalid fontsize karg\"; }\n\n        ++msgidx;\n    }\n\n    if (vars.size() <= msgidx)\n        return \"not enough args\";\n\n    const auto MESSAGE = vars.join(\" \", msgidx);\n\n    g_pHyprNotificationOverlay->addNotification(MESSAGE, color, time, sc<eIcons>(icon), fontsize);\n\n    return \"ok\";\n}\n\nstatic std::string dispatchDismissNotify(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n\n    int      amount = -1;\n\n    if (vars.size() > 1) {\n        const auto AMOUNT = vars[1];\n        if (!isNumber(AMOUNT))\n            return \"invalid arg 1\";\n\n        try {\n            amount = std::stoi(AMOUNT);\n        } catch (std::exception& e) { return \"invalid arg 1\"; }\n    }\n\n    g_pHyprNotificationOverlay->dismissNotifications(amount);\n\n    return \"ok\";\n}\n\nstatic std::string getIsLocked(eHyprCtlOutputFormat format, std::string request) {\n    std::string lockedStr = g_pSessionLockManager->isSessionLocked() ? \"true\" : \"false\";\n    if (format == eHyprCtlOutputFormat::FORMAT_JSON)\n        lockedStr = std::format(R\"#(\n{{\n    \"locked\": {}\n}}\n)#\",\n                                lockedStr);\n    return lockedStr;\n}\n\nstatic std::string getDescriptions(eHyprCtlOutputFormat format, std::string request) {\n    std::string json  = \"[\";\n    const auto& DESCS = g_pConfigManager->getAllDescriptions();\n\n    for (const auto& d : DESCS) {\n        json += d.jsonify() + \",\\n\";\n    }\n\n    json.pop_back();\n    json.pop_back();\n\n    json += \"]\\n\";\n    return json;\n}\n\nstatic std::string submapRequest(eHyprCtlOutputFormat format, std::string request) {\n    std::string submap = g_pKeybindManager->getCurrentSubmap().name;\n    if (submap.empty())\n        submap = \"default\";\n\n    return format == FORMAT_JSON ? std::format(\"{{\\\"{}\\\"}}\\n\", escapeJSONStrings(submap)) : (submap + \"\\n\");\n}\n\nstatic std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) {\n    CVarList vars(request, 0, ' ');\n\n    if (vars.size() > 2)\n        return \"too many args\";\n\n    if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : \"\"))\n        return format == FORMAT_JSON ? \"{\\\"ok\\\": true}\" : \"ok\";\n    else\n        return format == FORMAT_JSON ? \"{\\\"ok\\\": false}\" : \"error\";\n}\n\nCHyprCtl::CHyprCtl() {\n    registerCommand(SHyprCtlCommand{\"workspaces\", true, workspacesRequest});\n    registerCommand(SHyprCtlCommand{\"workspacerules\", true, workspaceRulesRequest});\n    registerCommand(SHyprCtlCommand{\"activeworkspace\", true, activeWorkspaceRequest});\n    registerCommand(SHyprCtlCommand{\"clients\", true, clientsRequest});\n    registerCommand(SHyprCtlCommand{\"kill\", true, killRequest});\n    registerCommand(SHyprCtlCommand{\"activewindow\", true, activeWindowRequest});\n    registerCommand(SHyprCtlCommand{\"layers\", true, layersRequest});\n    registerCommand(SHyprCtlCommand{\"version\", true, versionRequest});\n    registerCommand(SHyprCtlCommand{\"devices\", true, devicesRequest});\n    registerCommand(SHyprCtlCommand{\"splash\", true, splashRequest});\n    registerCommand(SHyprCtlCommand{\"cursorpos\", true, cursorPosRequest});\n    registerCommand(SHyprCtlCommand{\"binds\", true, bindsRequest});\n    registerCommand(SHyprCtlCommand{\"globalshortcuts\", true, globalShortcutsRequest});\n    registerCommand(SHyprCtlCommand{\"systeminfo\", true, systemInfoRequest});\n    registerCommand(SHyprCtlCommand{\"animations\", true, animationsRequest});\n    registerCommand(SHyprCtlCommand{\"rollinglog\", true, rollinglogRequest});\n    registerCommand(SHyprCtlCommand{\"configerrors\", true, configErrorsRequest});\n    registerCommand(SHyprCtlCommand{\"locked\", true, getIsLocked});\n    registerCommand(SHyprCtlCommand{\"descriptions\", true, getDescriptions});\n    registerCommand(SHyprCtlCommand{\"submap\", true, submapRequest});\n\n    registerCommand(SHyprCtlCommand{.name = \"reloadshaders\", .exact = false, .fn = reloadShaders});\n    registerCommand(SHyprCtlCommand{\"monitors\", false, monitorsRequest});\n    registerCommand(SHyprCtlCommand{\"reload\", false, reloadRequest});\n    registerCommand(SHyprCtlCommand{\"plugin\", false, dispatchPlugin});\n    registerCommand(SHyprCtlCommand{\"notify\", false, dispatchNotify});\n    registerCommand(SHyprCtlCommand{\"dismissnotify\", false, dispatchDismissNotify});\n    registerCommand(SHyprCtlCommand{\"getprop\", false, dispatchGetProp});\n    registerCommand(SHyprCtlCommand{\"seterror\", false, dispatchSeterror});\n    registerCommand(SHyprCtlCommand{\"switchxkblayout\", false, switchXKBLayoutRequest});\n    registerCommand(SHyprCtlCommand{\"output\", false, dispatchOutput});\n    registerCommand(SHyprCtlCommand{\"dispatch\", false, dispatchRequest});\n    registerCommand(SHyprCtlCommand{\"keyword\", false, dispatchKeyword});\n    registerCommand(SHyprCtlCommand{\"setcursor\", false, dispatchSetCursor});\n    registerCommand(SHyprCtlCommand{\"getoption\", false, dispatchGetOption});\n    registerCommand(SHyprCtlCommand{\"decorations\", false, decorationRequest});\n    registerCommand(SHyprCtlCommand{\"[[BATCH]]\", false, dispatchBatch});\n\n    startHyprCtlSocket();\n}\n\nCHyprCtl::~CHyprCtl() {\n    if (m_eventSource)\n        wl_event_source_remove(m_eventSource);\n    if (!m_socketPath.empty())\n        unlink(m_socketPath.c_str());\n}\n\nSP<SHyprCtlCommand> CHyprCtl::registerCommand(SHyprCtlCommand cmd) {\n    return m_commands.emplace_back(makeShared<SHyprCtlCommand>(cmd));\n}\n\nvoid CHyprCtl::unregisterCommand(const SP<SHyprCtlCommand>& cmd) {\n    std::erase(m_commands, cmd);\n}\n\nstd::string CHyprCtl::getReply(std::string request) {\n    auto format                          = eHyprCtlOutputFormat::FORMAT_NORMAL;\n    bool reloadAll                       = false;\n    m_currentRequestParams.all           = false;\n    m_currentRequestParams.sysInfoConfig = false;\n\n    // process flags for non-batch requests\n    if (!request.starts_with(\"[[BATCH]]\") && request.contains(\"/\")) {\n        long unsigned int sepIndex = 0;\n        for (const auto& c : request) {\n            if (c == '/') { // stop at separator\n                break;\n            }\n\n            // after whitespace assume the first word as a keyword,\n            // so its value can have slashes (e.g., a path)\n            if (c == ' ') {\n                sepIndex = request.size();\n                break;\n            }\n\n            sepIndex++;\n\n            if (c == 'j')\n                format = eHyprCtlOutputFormat::FORMAT_JSON;\n            else if (c == 'r')\n                reloadAll = true;\n            else if (c == 'a')\n                m_currentRequestParams.all = true;\n            else if (c == 'c')\n                m_currentRequestParams.sysInfoConfig = true;\n        }\n\n        if (sepIndex < request.size())\n            request = request.substr(sepIndex + 1); // remove flags and separator so we can compare the rest of the string\n    }\n\n    std::string result = \"\";\n\n    // parse exact cmds first, then non-exact.\n    for (auto const& cmd : m_commands) {\n        if (!cmd->exact)\n            continue;\n\n        if (cmd->name == request) {\n            result = cmd->fn(format, request);\n            break;\n        }\n    }\n\n    if (result.empty())\n        for (auto const& cmd : m_commands) {\n            if (cmd->exact)\n                continue;\n\n            if (request.starts_with(cmd->name)) {\n                result = cmd->fn(format, request);\n                break;\n            }\n        }\n\n    if (result.empty())\n        return \"unknown request\";\n\n    if (reloadAll) {\n        g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords\n\n        g_pInputManager->setKeyboardLayout();     // update kb layout\n        g_pInputManager->setPointerConfigs();     // update mouse cfgs\n        g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs\n        g_pInputManager->setTabletConfigs();      // update tablets\n\n        g_pHyprRenderer->m_reloadScreenShader = true;\n\n        for (auto const& m : g_pCompositor->m_monitors) {\n            if (m)\n                m->m_blurFBDirty = true;\n        }\n\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped || !w->m_workspace || !w->m_workspace->isVisible())\n                continue;\n\n            Desktop::Rule::ruleEngine()->updateAllRules();\n        }\n\n        for (const auto& ws : g_pCompositor->getWorkspaces()) {\n            if (!ws)\n                continue;\n\n            ws->updateWindows();\n            ws->updateWindowData();\n            ws->updateWindowDecos();\n        }\n\n        for (auto const& m : g_pCompositor->m_monitors) {\n            g_pHyprRenderer->damageMonitor(m);\n        }\n    }\n\n    return result;\n}\n\nstd::string CHyprCtl::makeDynamicCall(const std::string& input) {\n    return getReply(input);\n}\n\nstatic bool successWrite(int fd, const std::string& data, bool needLog = true) {\n    size_t                 totalWritten = 0;\n    size_t                 remaining    = data.length();\n    size_t                 waitsDone    = 0;\n    constexpr const size_t MAX_WAITS    = 20; // 2000µs = 2ms\n\n    while (totalWritten < data.length()) {\n        ssize_t written = write(fd, data.c_str() + totalWritten, remaining);\n\n        if (waitsDone > MAX_WAITS) {\n            Log::logger->log(Log::ERR, \"Couldn't write to socket. Buffer was full and the client couldn't read in time.\");\n            return false;\n        }\n\n        if (written < 0) {\n            if (errno == EAGAIN || errno == EWOULDBLOCK) {\n                // socket buffer full, wait a bit and retry\n                std::this_thread::sleep_for(std::chrono::microseconds(100));\n                waitsDone++;\n                continue;\n            }\n            if (needLog)\n                Log::logger->log(Log::ERR, \"Couldn't write to socket. Error: {}\", strerror(errno));\n            return false;\n        }\n\n        waitsDone = 0;\n\n        totalWritten += written;\n        remaining -= written;\n    }\n\n    return true;\n}\n\nstatic void runWritingDebugLogThread(const int conn) {\n    using namespace std::chrono_literals;\n    Log::logger->log(Log::DEBUG, \"In followlog thread, got connection, start writing: {}\", conn);\n    //will be finished, when reading side close connection\n    std::thread([conn]() {\n        while (Log::SRollingLogFollow::get().isRunning()) {\n            if (Log::SRollingLogFollow::get().isEmpty(conn)) {\n                std::this_thread::sleep_for(1000ms);\n                continue;\n            }\n\n            auto line = Log::SRollingLogFollow::get().getLog(conn);\n            if (!successWrite(conn, line))\n                // We cannot write, when connection is closed. So thread will successfully exit by itself\n                break;\n\n            std::this_thread::sleep_for(100ms);\n        }\n        close(conn);\n        Log::SRollingLogFollow::get().stopFor(conn);\n    }).detach();\n}\n\nstatic bool isFollowUpRollingLogRequest(const std::string& request) {\n    return request.contains(\"rollinglog\") && request.contains(\"f\");\n}\n\nstatic int hyprCtlFDTick(int fd, uint32_t mask, void* data) {\n    if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP)\n        return 0;\n\n    if (!g_pHyprCtl->m_socketFD.isValid())\n        return 0;\n\n    sockaddr_in            clientAddress;\n    socklen_t              clientSize = sizeof(clientAddress);\n\n    const auto             ACCEPTEDCONNECTION = accept4(g_pHyprCtl->m_socketFD.get(), rc<sockaddr*>(&clientAddress), &clientSize, SOCK_CLOEXEC);\n\n    std::array<char, 1024> readBuffer;\n\n    // try to get creds\n    CRED_T   creds;\n    uint32_t len = sizeof(creds);\n    if (getsockopt(ACCEPTEDCONNECTION, CRED_LVL, CRED_OPT, &creds, &len) == -1)\n        Log::logger->log(Log::ERR, \"Hyprctl: failed to get peer creds\");\n    else {\n        g_pHyprCtl->m_currentRequestParams.pid = creds.CRED_PID;\n        Log::logger->log(Log::DEBUG, \"Hyprctl: new connection from pid {}\", creds.CRED_PID);\n    }\n\n    //\n    pollfd pollfds[1] = {\n        {\n            .fd     = ACCEPTEDCONNECTION,\n            .events = POLLIN,\n        },\n    };\n\n    int ret = poll(pollfds, 1, 5000);\n\n    if (ret <= 0) {\n        close(ACCEPTEDCONNECTION);\n        return 0;\n    }\n\n    std::string request;\n    while (true) {\n        readBuffer.fill(0);\n        auto messageSize = read(ACCEPTEDCONNECTION, readBuffer.data(), 1023);\n        if (messageSize < 1)\n            break;\n        std::string recvd = readBuffer.data();\n        request += recvd;\n        if (messageSize < 1023)\n            break;\n    }\n\n    std::string reply = \"\";\n\n    try {\n        reply = g_pHyprCtl->getReply(request);\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Error in request: {}\", e.what());\n        reply = \"Err: \" + std::string(e.what());\n    }\n\n    if (g_pHyprCtl->m_currentRequestParams.pendingPromise) {\n        // we have a promise pending\n        g_pHyprCtl->m_currentRequestParams.pendingPromise->then([ACCEPTEDCONNECTION, request](SP<CPromiseResult<std::string>> result) {\n            const auto RES = result->hasError() ? result->error() : result->result();\n            successWrite(ACCEPTEDCONNECTION, RES);\n\n            // No rollinglog or ensureMonitor here. These are only for plugins for now.\n\n            close(ACCEPTEDCONNECTION);\n        });\n\n        g_pHyprCtl->m_currentRequestParams.pendingPromise.reset();\n    } else {\n        successWrite(ACCEPTEDCONNECTION, reply);\n\n        if (isFollowUpRollingLogRequest(request)) {\n            Log::logger->log(Log::DEBUG, \"Followup rollinglog request received. Starting thread to write to socket.\");\n            Log::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION);\n            runWritingDebugLogThread(ACCEPTEDCONNECTION);\n            Log::logger->log(Log::DEBUG, Log::SRollingLogFollow::get().debugInfo());\n        } else\n            close(ACCEPTEDCONNECTION);\n\n        if (g_pConfigManager->m_wantsMonitorReload)\n            g_pConfigManager->ensureMonitorStatus();\n\n        g_pHyprCtl->m_currentRequestParams.pid = 0;\n    }\n\n    return 0;\n}\n\nvoid CHyprCtl::startHyprCtlSocket() {\n    m_socketFD = CFileDescriptor{socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)};\n\n    if (!m_socketFD.isValid()) {\n        Log::logger->log(Log::ERR, \"Couldn't start the Hyprland Socket. (1) IPC will not work.\");\n        return;\n    }\n\n    sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX};\n\n    m_socketPath = g_pCompositor->m_instancePath + \"/.socket.sock\";\n\n    snprintf(SERVERADDRESS.sun_path, sizeof(SERVERADDRESS.sun_path), \"%s\", m_socketPath.c_str());\n\n    if (bind(m_socketFD.get(), rc<sockaddr*>(&SERVERADDRESS), SUN_LEN(&SERVERADDRESS)) < 0) {\n        Log::logger->log(Log::ERR, \"Couldn't start the Hyprland Socket. (2) IPC will not work.\");\n        return;\n    }\n\n    // 10 max queued.\n    listen(m_socketFD.get(), 10);\n\n    Log::logger->log(Log::DEBUG, \"Hypr socket started at {}\", m_socketPath);\n\n    m_eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_socketFD.get(), WL_EVENT_READABLE, hyprCtlFDTick, nullptr);\n}\n"
  },
  {
    "path": "src/debug/HyprCtl.hpp",
    "content": "#pragma once\n\n#include <fstream>\n#include \"../helpers/MiscFunctions.hpp\"\n#include \"../helpers/defer/Promise.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include <functional>\n#include <sys/types.h>\n#include <hyprutils/os/FileDescriptor.hpp>\n\n// exposed for main.cpp\nstd::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request);\nstd::string versionRequest(eHyprCtlOutputFormat format, std::string request);\n\nclass CHyprCtl {\n  public:\n    CHyprCtl();\n    ~CHyprCtl();\n\n    std::string                    makeDynamicCall(const std::string& input);\n    SP<SHyprCtlCommand>            registerCommand(SHyprCtlCommand cmd);\n    void                           unregisterCommand(const SP<SHyprCtlCommand>& cmd);\n    std::string                    getReply(std::string);\n\n    Hyprutils::OS::CFileDescriptor m_socketFD;\n\n    struct {\n        bool                      all              = false;\n        bool                      sysInfoConfig    = false;\n        bool                      isDynamicKeyword = false;\n        pid_t                     pid              = 0;\n        SP<CPromise<std::string>> pendingPromise;\n    } m_currentRequestParams;\n\n    static std::string getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format);\n    static std::string getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format);\n    static std::string getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);\n    static std::string getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);\n    static std::string getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);\n    static std::string getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);\n\n  private:\n    void                             startHyprCtlSocket();\n\n    std::vector<SP<SHyprCtlCommand>> m_commands;\n    wl_event_source*                 m_eventSource = nullptr;\n    std::string                      m_socketPath;\n};\n\ninline UP<CHyprCtl> g_pHyprCtl;\n"
  },
  {
    "path": "src/debug/HyprDebugOverlay.cpp",
    "content": "#include <pango/pangocairo.h>\n#include \"HyprDebugOverlay.hpp\"\n#include \"config/ConfigValue.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../render/pass/TexPassElement.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n\nCHyprDebugOverlay::CHyprDebugOverlay() {\n    m_texture = g_pHyprRenderer->createTexture();\n}\n\nvoid CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) {\n    static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n\n    if (!*PDEBUGOVERLAY)\n        return;\n\n    m_lastRenderTimes.emplace_back(durationUs / 1000.f);\n\n    if (m_lastRenderTimes.size() > sc<long unsigned int>(pMonitor->m_refreshRate))\n        m_lastRenderTimes.pop_front();\n\n    if (!m_monitor)\n        m_monitor = pMonitor;\n}\n\nvoid CHyprMonitorDebugOverlay::renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs) {\n    static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n\n    if (!*PDEBUGOVERLAY)\n        return;\n\n    m_lastRenderTimesNoOverlay.emplace_back(durationUs / 1000.f);\n\n    if (m_lastRenderTimesNoOverlay.size() > sc<long unsigned int>(pMonitor->m_refreshRate))\n        m_lastRenderTimesNoOverlay.pop_front();\n\n    if (!m_monitor)\n        m_monitor = pMonitor;\n}\n\nvoid CHyprMonitorDebugOverlay::frameData(PHLMONITOR pMonitor) {\n    static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n\n    if (!*PDEBUGOVERLAY)\n        return;\n\n    m_lastFrametimes.emplace_back(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - m_lastFrame).count() / 1000.f);\n\n    if (m_lastFrametimes.size() > sc<long unsigned int>(pMonitor->m_refreshRate))\n        m_lastFrametimes.pop_front();\n\n    m_lastFrame = std::chrono::high_resolution_clock::now();\n\n    if (!m_monitor)\n        m_monitor = pMonitor;\n\n    // anim data too\n    const auto PMONITORFORTICKS = g_pHyprRenderer->m_mostHzMonitor ? g_pHyprRenderer->m_mostHzMonitor.lock() : Desktop::focusState()->monitor();\n    if (PMONITORFORTICKS == pMonitor) {\n        if (m_lastAnimationTicks.size() > sc<long unsigned int>(PMONITORFORTICKS->m_refreshRate))\n            m_lastAnimationTicks.pop_front();\n\n        m_lastAnimationTicks.push_back(g_pAnimationManager->m_lastTickTimeMs);\n    }\n}\n\nint CHyprMonitorDebugOverlay::draw(int offset) {\n\n    if (!m_monitor)\n        return 0;\n\n    // get avg fps\n    float avgFrametime = 0;\n    float maxFrametime = 0;\n    float minFrametime = 9999;\n    for (auto const& ft : m_lastFrametimes) {\n        if (ft > maxFrametime)\n            maxFrametime = ft;\n        if (ft < minFrametime)\n            minFrametime = ft;\n        avgFrametime += ft;\n    }\n    float varFrametime = maxFrametime - minFrametime;\n    avgFrametime /= m_lastFrametimes.empty() ? 1 : m_lastFrametimes.size();\n\n    float avgRenderTime = 0;\n    float maxRenderTime = 0;\n    float minRenderTime = 9999;\n    for (auto const& rt : m_lastRenderTimes) {\n        if (rt > maxRenderTime)\n            maxRenderTime = rt;\n        if (rt < minRenderTime)\n            minRenderTime = rt;\n        avgRenderTime += rt;\n    }\n    float varRenderTime = maxRenderTime - minRenderTime;\n    avgRenderTime /= m_lastRenderTimes.empty() ? 1 : m_lastRenderTimes.size();\n\n    float avgRenderTimeNoOverlay = 0;\n    float maxRenderTimeNoOverlay = 0;\n    float minRenderTimeNoOverlay = 9999;\n    for (auto const& rt : m_lastRenderTimesNoOverlay) {\n        if (rt > maxRenderTimeNoOverlay)\n            maxRenderTimeNoOverlay = rt;\n        if (rt < minRenderTimeNoOverlay)\n            minRenderTimeNoOverlay = rt;\n        avgRenderTimeNoOverlay += rt;\n    }\n    float varRenderTimeNoOverlay = maxRenderTimeNoOverlay - minRenderTimeNoOverlay;\n    avgRenderTimeNoOverlay /= m_lastRenderTimes.empty() ? 1 : m_lastRenderTimes.size();\n\n    float avgAnimMgrTick = 0;\n    float maxAnimMgrTick = 0;\n    float minAnimMgrTick = 9999;\n    for (auto const& at : m_lastAnimationTicks) {\n        if (at > maxAnimMgrTick)\n            maxAnimMgrTick = at;\n        if (at < minAnimMgrTick)\n            minAnimMgrTick = at;\n        avgAnimMgrTick += at;\n    }\n    float varAnimMgrTick = maxAnimMgrTick - minAnimMgrTick;\n    avgAnimMgrTick /= m_lastAnimationTicks.empty() ? 1 : m_lastAnimationTicks.size();\n\n    const float           FPS      = 1.f / (avgFrametime / 1000.f); // frametimes are in ms\n    const float           idealFPS = m_lastFrametimes.size();\n\n    static auto           fontFamily = CConfigValue<std::string>(\"misc:font_family\");\n    PangoLayout*          layoutText = pango_cairo_create_layout(g_pDebugOverlay->m_cairo);\n    PangoFontDescription* pangoFD    = pango_font_description_new();\n\n    pango_font_description_set_family(pangoFD, (*fontFamily).c_str());\n    pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL);\n    pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL);\n\n    float maxTextW = 0;\n    int   fontSize = 0;\n    auto  cr       = g_pDebugOverlay->m_cairo;\n\n    auto  showText = [cr, layoutText, pangoFD, &maxTextW, &fontSize](const char* text, int size) {\n        if (fontSize != size) {\n            pango_font_description_set_absolute_size(pangoFD, size * PANGO_SCALE);\n            pango_layout_set_font_description(layoutText, pangoFD);\n            fontSize = size;\n        }\n\n        pango_layout_set_text(layoutText, text, -1);\n        pango_cairo_show_layout(cr, layoutText);\n\n        int textW = 0, textH = 0;\n        pango_layout_get_size(layoutText, &textW, &textH);\n        textW /= PANGO_SCALE;\n        textH /= PANGO_SCALE;\n        if (textW > maxTextW)\n            maxTextW = textW;\n\n        // move to next line\n        cairo_rel_move_to(cr, 0, fontSize + 1);\n    };\n\n    const int MARGIN_TOP  = 8;\n    const int MARGIN_LEFT = 4;\n    cairo_move_to(cr, MARGIN_LEFT, MARGIN_TOP + offset);\n    cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 1.f, 1.f, 1.f);\n\n    std::string text;\n    showText(m_monitor->m_name.c_str(), 10);\n\n    if (FPS > idealFPS * 0.95f)\n        cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 0.2f, 1.f, 0.2f, 1.f);\n    else if (FPS > idealFPS * 0.8f)\n        cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 1.f, 0.2f, 1.f);\n    else\n        cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 0.2f, 0.2f, 1.f);\n\n    text = std::format(\"{} FPS\", sc<int>(FPS));\n    showText(text.c_str(), 16);\n\n    cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 1.f, 1.f, 1.f);\n\n    text = std::format(\"Avg Frametime: {:.2f}ms (var {:.2f}ms)\", avgFrametime, varFrametime);\n    showText(text.c_str(), 10);\n\n    text = std::format(\"Avg Rendertime: {:.2f}ms (var {:.2f}ms)\", avgRenderTime, varRenderTime);\n    showText(text.c_str(), 10);\n\n    text = std::format(\"Avg Rendertime (No Overlay): {:.2f}ms (var {:.2f}ms)\", avgRenderTimeNoOverlay, varRenderTimeNoOverlay);\n    showText(text.c_str(), 10);\n\n    text = std::format(\"Avg Anim Tick: {:.2f}ms (var {:.2f}ms) ({:.2f} TPS)\", avgAnimMgrTick, varAnimMgrTick, 1.0 / (avgAnimMgrTick / 1000.0));\n    showText(text.c_str(), 10);\n\n    pango_font_description_free(pangoFD);\n    g_object_unref(layoutText);\n\n    double posX = 0, posY = 0;\n    cairo_get_current_point(cr, &posX, &posY);\n\n    g_pHyprRenderer->damageBox(m_lastDrawnBox);\n    m_lastDrawnBox = {sc<int>(g_pCompositor->m_monitors.front()->m_position.x) + MARGIN_LEFT - 1,\n                      sc<int>(g_pCompositor->m_monitors.front()->m_position.y) + offset + MARGIN_TOP - 1, sc<int>(maxTextW) + 2, posY - offset - MARGIN_TOP + 2};\n    g_pHyprRenderer->damageBox(m_lastDrawnBox);\n\n    return posY - offset;\n}\n\nvoid CHyprDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) {\n    static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n\n    if (!*PDEBUGOVERLAY)\n        return;\n\n    m_monitorOverlays[pMonitor].renderData(pMonitor, durationUs);\n}\n\nvoid CHyprDebugOverlay::renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs) {\n    static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n\n    if (!*PDEBUGOVERLAY)\n        return;\n\n    m_monitorOverlays[pMonitor].renderDataNoOverlay(pMonitor, durationUs);\n}\n\nvoid CHyprDebugOverlay::frameData(PHLMONITOR pMonitor) {\n    static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n\n    if (!*PDEBUGOVERLAY)\n        return;\n\n    m_monitorOverlays[pMonitor].frameData(pMonitor);\n}\n\nvoid CHyprDebugOverlay::draw() {\n\n    const auto PMONITOR = g_pCompositor->m_monitors.front();\n\n    if (!m_cairoSurface || !m_cairo) {\n        m_cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y);\n        m_cairo        = cairo_create(m_cairoSurface);\n    }\n\n    // clear the pixmap\n    cairo_save(m_cairo);\n    cairo_set_operator(m_cairo, CAIRO_OPERATOR_CLEAR);\n    cairo_paint(m_cairo);\n    cairo_restore(m_cairo);\n\n    // draw the things\n    int offsetY = 0;\n    for (auto const& m : g_pCompositor->m_monitors) {\n        offsetY += m_monitorOverlays[m].draw(offsetY);\n        offsetY += 5; // for padding between mons\n    }\n\n    cairo_surface_flush(m_cairoSurface);\n\n    // copy the data to an OpenGL texture we have\n    m_texture = g_pHyprRenderer->createTexture(m_cairoSurface);\n\n    CTexPassElement::SRenderData data;\n    data.tex = m_texture;\n    data.box = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y};\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n"
  },
  {
    "path": "src/debug/HyprDebugOverlay.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../render/Texture.hpp\"\n#include <cairo/cairo.h>\n#include <map>\n#include <deque>\n\nclass IHyprRenderer;\n\nclass CHyprMonitorDebugOverlay {\n  public:\n    int  draw(int offset);\n\n    void renderData(PHLMONITOR pMonitor, float durationUs);\n    void renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs);\n    void frameData(PHLMONITOR pMonitor);\n\n  private:\n    std::deque<float>                              m_lastFrametimes;\n    std::deque<float>                              m_lastRenderTimes;\n    std::deque<float>                              m_lastRenderTimesNoOverlay;\n    std::deque<float>                              m_lastAnimationTicks;\n    std::chrono::high_resolution_clock::time_point m_lastFrame;\n    PHLMONITORREF                                  m_monitor;\n    CBox                                           m_lastDrawnBox;\n\n    friend class IHyprRenderer;\n};\n\nclass CHyprDebugOverlay {\n  public:\n    CHyprDebugOverlay();\n    void draw();\n    void renderData(PHLMONITOR, float durationUs);\n    void renderDataNoOverlay(PHLMONITOR, float durationUs);\n    void frameData(PHLMONITOR);\n\n  private:\n    std::map<PHLMONITORREF, CHyprMonitorDebugOverlay> m_monitorOverlays;\n\n    cairo_surface_t*                                  m_cairoSurface = nullptr;\n    cairo_t*                                          m_cairo        = nullptr;\n\n    SP<ITexture>                                      m_texture;\n\n    friend class CHyprMonitorDebugOverlay;\n    friend class IHyprRenderer;\n};\n\ninline UP<CHyprDebugOverlay> g_pDebugOverlay;\n"
  },
  {
    "path": "src/debug/HyprNotificationOverlay.cpp",
    "content": "#include <numeric>\n#include <pango/pangocairo.h>\n#include \"HyprNotificationOverlay.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../render/pass/TexPassElement.hpp\"\n#include \"../event/EventBus.hpp\"\n\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../render/Renderer.hpp\"\n\nstatic inline auto iconBackendFromLayout(PangoLayout* layout) {\n    // preference: Nerd > FontAwesome > text\n    auto eIconBackendChecks = std::array<eIconBackend, 2>{ICONS_BACKEND_NF, ICONS_BACKEND_FA};\n    for (auto iconID : eIconBackendChecks) {\n        auto iconsText = std::ranges::fold_left(ICONS_ARRAY[iconID], std::string(), std::plus<>());\n        pango_layout_set_text(layout, iconsText.c_str(), -1);\n        if (pango_layout_get_unknown_glyphs_count(layout) == 0)\n            return iconID;\n    }\n    return ICONS_BACKEND_NONE;\n}\n\nCHyprNotificationOverlay::CHyprNotificationOverlay() {\n    static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) {\n        if (m_notifications.empty())\n            return;\n\n        g_pHyprRenderer->damageBox(m_lastDamage);\n    });\n}\n\nCHyprNotificationOverlay::~CHyprNotificationOverlay() {\n    if (m_cairo)\n        cairo_destroy(m_cairo);\n    if (m_cairoSurface)\n        cairo_surface_destroy(m_cairoSurface);\n}\n\nvoid CHyprNotificationOverlay::addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon, const float fontSize) {\n    const auto PNOTIF = m_notifications.emplace_back(makeUnique<SNotification>()).get();\n\n    PNOTIF->text  = icon != eIcons::ICON_NONE ? \" \" + text /* tiny bit of padding otherwise icon touches text */ : text;\n    PNOTIF->color = color == CHyprColor(0) ? ICONS_COLORS[icon] : color;\n    PNOTIF->started.reset();\n    PNOTIF->timeMs   = timeMs;\n    PNOTIF->icon     = icon;\n    PNOTIF->fontSize = fontSize;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        g_pCompositor->scheduleFrameForMonitor(m);\n    }\n}\n\nvoid CHyprNotificationOverlay::dismissNotifications(const int amount) {\n    if (amount == -1)\n        m_notifications.clear();\n    else {\n        const int AMT = std::min(amount, sc<int>(m_notifications.size()));\n\n        for (int i = 0; i < AMT; ++i) {\n            m_notifications.erase(m_notifications.begin());\n        }\n    }\n}\n\nCBox CHyprNotificationOverlay::drawNotifications(PHLMONITOR pMonitor) {\n    static constexpr auto ANIM_DURATION_MS   = 600.0;\n    static constexpr auto ANIM_LAG_MS        = 100.0;\n    static constexpr auto NOTIF_LEFTBAR_SIZE = 5.0;\n    static constexpr auto ICON_PAD           = 3.0;\n    static constexpr auto ICON_SCALE         = 0.9;\n    static constexpr auto GRADIENT_SIZE      = 60.0;\n\n    float                 offsetY  = 10;\n    float                 maxWidth = 0;\n\n    const auto            SCALE   = pMonitor->m_scale;\n    const auto            MONSIZE = pMonitor->m_transformedSize;\n\n    static auto           fontFamily = CConfigValue<std::string>(\"misc:font_family\");\n\n    PangoLayout*          layout  = pango_cairo_create_layout(m_cairo);\n    PangoFontDescription* pangoFD = pango_font_description_new();\n\n    pango_font_description_set_family(pangoFD, (*fontFamily).c_str());\n    pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL);\n    pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL);\n\n    const auto iconBackendID = iconBackendFromLayout(layout);\n    const auto PBEZIER       = g_pAnimationManager->getBezier(\"default\");\n\n    for (auto const& notif : m_notifications) {\n        const auto ICONPADFORNOTIF = notif->icon == ICON_NONE ? 0 : ICON_PAD;\n        const auto FONTSIZE        = std::clamp(sc<int>(notif->fontSize * ((pMonitor->m_pixelSize.x * SCALE) / 1920.f)), 8, 40);\n\n        // first rect (bg, col)\n        const float FIRSTRECTANIMP =\n            (notif->started.getMillis() > (ANIM_DURATION_MS - ANIM_LAG_MS) ?\n                 (notif->started.getMillis() > notif->timeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? notif->timeMs - notif->started.getMillis() : (ANIM_DURATION_MS - ANIM_LAG_MS)) :\n                 notif->started.getMillis()) /\n            (ANIM_DURATION_MS - ANIM_LAG_MS);\n\n        const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP);\n\n        // second rect (fg, black)\n        const float SECONDRECTANIMP = (notif->started.getMillis() > ANIM_DURATION_MS ?\n                                           (notif->started.getMillis() > notif->timeMs - ANIM_DURATION_MS ? notif->timeMs - notif->started.getMillis() : ANIM_DURATION_MS) :\n                                           notif->started.getMillis()) /\n            ANIM_DURATION_MS;\n\n        const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP);\n\n        // third rect (horiz, col)\n        const float THIRDRECTPERC = notif->started.getMillis() / notif->timeMs;\n\n        // get text size\n        const auto ICON      = ICONS_ARRAY[iconBackendID][notif->icon];\n        const auto ICONCOLOR = ICONS_COLORS[notif->icon];\n\n        int        iconW = 0, iconH = 0;\n        pango_font_description_set_absolute_size(pangoFD, PANGO_SCALE * FONTSIZE * ICON_SCALE);\n        pango_layout_set_font_description(layout, pangoFD);\n        pango_layout_set_text(layout, ICON.c_str(), -1);\n        pango_layout_get_size(layout, &iconW, &iconH);\n        iconW /= PANGO_SCALE;\n        iconH /= PANGO_SCALE;\n\n        int textW = 0, textH = 0;\n        pango_font_description_set_absolute_size(pangoFD, PANGO_SCALE * FONTSIZE);\n        pango_layout_set_font_description(layout, pangoFD);\n        pango_layout_set_text(layout, notif->text.c_str(), -1);\n        pango_layout_get_size(layout, &textW, &textH);\n        textW /= PANGO_SCALE;\n        textH /= PANGO_SCALE;\n\n        const auto NOTIFSIZE = Vector2D{textW + 20.0 + iconW + 2 * ICONPADFORNOTIF, textH + 10.0};\n\n        // draw rects\n        cairo_set_source_rgba(m_cairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a);\n        cairo_rectangle(m_cairo, MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, NOTIFSIZE.y);\n        cairo_fill(m_cairo);\n\n        cairo_set_source_rgb(m_cairo, 0.f, 0.f, 0.f);\n        cairo_rectangle(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC, offsetY, NOTIFSIZE.x * SECONDRECTPERC, NOTIFSIZE.y);\n        cairo_fill(m_cairo);\n\n        cairo_set_source_rgba(m_cairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a);\n        cairo_rectangle(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * (NOTIFSIZE.x - 6), 2);\n        cairo_fill(m_cairo);\n\n        // draw gradient\n        if (notif->icon != ICON_NONE) {\n            cairo_pattern_t* pattern;\n            pattern = cairo_pattern_create_linear(MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY,\n                                                  MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC + GRADIENT_SIZE, offsetY);\n            cairo_pattern_add_color_stop_rgba(pattern, 0, ICONCOLOR.r, ICONCOLOR.g, ICONCOLOR.b, ICONCOLOR.a / 3.0);\n            cairo_pattern_add_color_stop_rgba(pattern, 1, ICONCOLOR.r, ICONCOLOR.g, ICONCOLOR.b, 0);\n            cairo_rectangle(m_cairo, MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, GRADIENT_SIZE, NOTIFSIZE.y);\n            cairo_set_source(m_cairo, pattern);\n            cairo_fill(m_cairo);\n            cairo_pattern_destroy(pattern);\n\n            // draw icon\n            cairo_set_source_rgb(m_cairo, 1.f, 1.f, 1.f);\n            cairo_move_to(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + NOTIF_LEFTBAR_SIZE + ICONPADFORNOTIF - 1, offsetY - 2 + std::round((NOTIFSIZE.y - iconH) / 2.0));\n            pango_layout_set_text(layout, ICON.c_str(), -1);\n            pango_cairo_show_layout(m_cairo, layout);\n        }\n\n        // draw text\n        cairo_set_source_rgb(m_cairo, 1.f, 1.f, 1.f);\n        cairo_move_to(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + NOTIF_LEFTBAR_SIZE + iconW + 2 * ICONPADFORNOTIF, offsetY - 2 + std::round((NOTIFSIZE.y - textH) / 2.0));\n        pango_layout_set_text(layout, notif->text.c_str(), -1);\n        pango_cairo_show_layout(m_cairo, layout);\n\n        // adjust offset and move on\n        offsetY += NOTIFSIZE.y + 10;\n\n        if (maxWidth < NOTIFSIZE.x)\n            maxWidth = NOTIFSIZE.x;\n    }\n\n    pango_font_description_free(pangoFD);\n    g_object_unref(layout);\n\n    // cleanup notifs\n    std::erase_if(m_notifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; });\n\n    return CBox{sc<int>(pMonitor->m_position.x + pMonitor->m_size.x - maxWidth - 20), sc<int>(pMonitor->m_position.y), sc<int>(maxWidth) + 20, sc<int>(offsetY) + 10};\n}\n\nvoid CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) {\n\n    const auto MONSIZE = pMonitor->m_transformedSize;\n\n    if (m_lastMonitor != pMonitor || m_lastSize != MONSIZE || !m_cairo || !m_cairoSurface) {\n\n        if (m_cairo && m_cairoSurface) {\n            cairo_destroy(m_cairo);\n            cairo_surface_destroy(m_cairoSurface);\n        }\n\n        m_cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, MONSIZE.x, MONSIZE.y);\n        m_cairo        = cairo_create(m_cairoSurface);\n        m_lastMonitor  = pMonitor;\n        m_lastSize     = MONSIZE;\n    }\n\n    // Draw the notifications\n    if (m_notifications.empty())\n        return;\n\n    // Render to the monitor\n\n    // clear the pixmap\n    cairo_save(m_cairo);\n    cairo_set_operator(m_cairo, CAIRO_OPERATOR_CLEAR);\n    cairo_paint(m_cairo);\n    cairo_restore(m_cairo);\n\n    cairo_surface_flush(m_cairoSurface);\n\n    CBox damage = drawNotifications(pMonitor);\n\n    g_pHyprRenderer->damageBox(damage);\n    g_pHyprRenderer->damageBox(m_lastDamage);\n\n    g_pCompositor->scheduleFrameForMonitor(pMonitor);\n\n    m_lastDamage = damage;\n\n    m_texture = g_pHyprRenderer->createTexture(m_cairoSurface);\n\n    CTexPassElement::SRenderData data;\n    data.tex = m_texture;\n    data.box = {0, 0, MONSIZE.x, MONSIZE.y};\n    data.a   = 1.F;\n\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n\nbool CHyprNotificationOverlay::hasAny() {\n    return !m_notifications.empty();\n}\n"
  },
  {
    "path": "src/debug/HyprNotificationOverlay.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/time/Timer.hpp\"\n#include \"../render/Texture.hpp\"\n#include \"../SharedDefs.hpp\"\n\n#include <vector>\n\n#include <cairo/cairo.h>\n\nenum eIconBackend : uint8_t {\n    ICONS_BACKEND_NONE = 0,\n    ICONS_BACKEND_NF,\n    ICONS_BACKEND_FA\n};\n\nstatic const std::array<std::array<std::string, ICON_NONE + 1>, 3 /* backends */> ICONS_ARRAY = {\n    std::array<std::string, ICON_NONE + 1>{\"[!]\", \"[i]\", \"[Hint]\", \"[Err]\", \"[?]\", \"[ok]\", \"\"},\n    std::array<std::string, ICON_NONE + 1>{\"\", \"\", \"\", \"\", \"\", \"󰸞\", \"\"}, std::array<std::string, ICON_NONE + 1>{\"\", \"\", \"\", \"\", \"\", \"\"}};\nstatic const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0},\n                                                                   CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0},\n                                                                   CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0},\n                                                                   CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0},\n                                                                   CHyprColor{255 / 255.0, 204 / 255.0, 153 / 255.0, 1.0},\n                                                                   CHyprColor{128 / 255.0, 255 / 255.0, 128 / 255.0, 1.0},\n                                                                   CHyprColor{0, 0, 0, 1.0}};\n\nstruct SNotification {\n    std::string text = \"\";\n    CHyprColor  color;\n    CTimer      started;\n    float       timeMs   = 0;\n    eIcons      icon     = ICON_NONE;\n    float       fontSize = 13.f;\n};\n\nclass CHyprNotificationOverlay {\n  public:\n    CHyprNotificationOverlay();\n    ~CHyprNotificationOverlay();\n\n    void draw(PHLMONITOR pMonitor);\n    void addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon = ICON_NONE, const float fontSize = 13.f);\n    void dismissNotifications(const int amount);\n    bool hasAny();\n\n  private:\n    CBox                           drawNotifications(PHLMONITOR pMonitor);\n    CBox                           m_lastDamage;\n\n    std::vector<UP<SNotification>> m_notifications;\n\n    cairo_surface_t*               m_cairoSurface = nullptr;\n    cairo_t*                       m_cairo        = nullptr;\n\n    PHLMONITORREF                  m_lastMonitor;\n    Vector2D                       m_lastSize = Vector2D(-1, -1);\n\n    SP<ITexture>                   m_texture;\n};\n\ninline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay;\n"
  },
  {
    "path": "src/debug/TracyDefines.hpp",
    "content": "#pragma once\n\n#ifdef USE_TRACY_GPU\n\n#include \"log/Logger.hpp\"\n\n#include <GL/gl.h>\n#include <GLES2/gl2ext.h>\n\ninline PFNGLQUERYCOUNTEREXTPROC        glQueryCounter;\ninline PFNGLGETQUERYOBJECTIVEXTPROC    glGetQueryObjectiv;\ninline PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;\n\n#include \"../../subprojects/tracy/public/tracy/TracyOpenGL.hpp\"\n\n#define TRACY_GPU_CONTEXT TracyGpuContext\n#define TRACY_GPU_ZONE(e) TracyGpuZone(e)\n#define TRACY_GPU_COLLECT TracyGpuCollect\n\n#else\n\n#define TRACY_GPU_CONTEXT\n#define TRACY_GPU_ZONE(e)\n#define TRACY_GPU_COLLECT\n\n#endif"
  },
  {
    "path": "src/debug/crash/CrashReporter.cpp",
    "content": "#include \"CrashReporter.hpp\"\n#include <fcntl.h>\n#include <sys/utsname.h>\n#include <link.h>\n#include <ctime>\n#include <cerrno>\n#include <sys/stat.h>\n#include <filesystem>\n#include \"../../helpers/MiscFunctions.hpp\"\n\n#include \"../../plugins/PluginSystem.hpp\"\n#include \"SignalSafe.hpp\"\n\n#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)\n#include <sys/sysctl.h>\n#endif\n\nstatic char const* const MESSAGES[] = {\n    \"Sorry, didn't mean to...\",\n    \"This was an accident, I swear!\",\n    \"Calm down, it was a misinput! MISINPUT!\",\n    \"Oops\",\n    \"Vaxry is going to be upset.\",\n    \"Who tried dividing by zero?!\",\n    \"Maybe you should try dusting your PC in the meantime?\",\n    \"I tried so hard, and got so far...\",\n    \"I don't feel so good...\",\n    \"*thud*\",\n    \"Well this is awkward.\",\n    \"\\\"stable\\\"\",\n    \"I hope you didn't have any unsaved progress.\",\n    \"All these computers...\",\n    \"The math isn't mathing...\",\n    \"We've got an imposter in the code!\",\n    \"Well, at least the crash reporter didn't crash!\",\n    \"Everything's just fi-\",\n    \"Have you tried asking Hyprland politely not to crash?\",\n};\n\n// <random> is not async-signal-safe, fake it with time(NULL) instead\nstatic char const* getRandomMessage() {\n    return MESSAGES[time(nullptr) % (sizeof(MESSAGES) / sizeof(MESSAGES[0]))];\n}\n\n[[noreturn]] static inline void exitWithError(char const* err) {\n    write(STDERR_FILENO, err, strlen(err));\n    // perror() is not signal-safe, but we use it here\n    // because if the crash-handler already crashed, it can't get any worse.\n    perror(\"\");\n    abort();\n}\n\nvoid CrashReporter::createAndSaveCrash(int sig) {\n    int reportFd = -1;\n\n    // We're in the signal handler, so we *only* have stack memory.\n    // To save as much stack memory as possible,\n    // destroy things as soon as possible.\n    {\n        SignalSafe::CMaxLengthCString<255> reportPath;\n\n        const auto                         HOME       = SignalSafe::getenv(\"HOME\");\n        const auto                         CACHE_HOME = SignalSafe::getenv(\"XDG_CACHE_HOME\");\n\n        if (CACHE_HOME && CACHE_HOME[0] != '\\0') {\n            reportPath += CACHE_HOME;\n            reportPath += \"/hyprland\";\n        } else if (HOME && HOME[0] != '\\0') {\n            reportPath += HOME;\n            reportPath += \"/.cache/hyprland\";\n        } else {\n            exitWithError(\"$CACHE_HOME and $HOME not set, nowhere to report crash\\n\");\n            return;\n        }\n\n        int ret = mkdir(reportPath.getStr(), S_IRWXU);\n        if (ret < 0 && errno != EEXIST)\n            exitWithError(\"failed to mkdir() crash report directory\\n\");\n\n        reportPath += \"/hyprlandCrashReport\";\n        reportPath.writeNum(getpid());\n        reportPath += \".txt\";\n\n        {\n            SignalSafe::CBufFileWriter<64> stderrOut(STDERR_FILENO);\n            stderrOut += \"Hyprland has crashed :( Consult the crash report at \";\n            if (!reportPath.boundsExceeded())\n                stderrOut += reportPath.getStr();\n            else\n                stderrOut += \"[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]\";\n\n            stderrOut += \" for more information.\\n\";\n            stderrOut.flush();\n        }\n\n        reportFd = open(reportPath.getStr(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);\n        if (reportFd < 0)\n            exitWithError(\"Failed to open crash report path for writing\");\n    }\n    SignalSafe::CBufFileWriter<512> finalCrashReport(reportFd);\n\n    finalCrashReport += \"--------------------------------------------\\n   Hyprland Crash Report\\n--------------------------------------------\\n\";\n    finalCrashReport += getRandomMessage();\n    finalCrashReport += \"\\n\\n\";\n\n    finalCrashReport += \"Hyprland received signal \";\n    finalCrashReport.writeNum(sig);\n    finalCrashReport += '(';\n    finalCrashReport += SignalSafe::strsignal(sig);\n    finalCrashReport += \")\\nVersion: \";\n    finalCrashReport += GIT_COMMIT_HASH;\n    finalCrashReport += \"\\nTag: \";\n    finalCrashReport += GIT_TAG;\n    finalCrashReport += \"\\nDate: \";\n    finalCrashReport += GIT_COMMIT_DATE;\n    finalCrashReport += \"\\nFlags:\\n\";\n#if ISDEBUG\n    finalCrashReport += \"debug\\n\";\n#endif\n#ifdef NO_XWAYLAND\n    finalCrashReport += \"no xwayland\\n\";\n#endif\n    finalCrashReport += \"\\n\";\n\n    if (g_pPluginSystem && g_pPluginSystem->pluginCount() > 0) {\n        finalCrashReport += \"Hyprland seems to be running with plugins. This crash might not be Hyprland's fault.\\nPlugins:\\n\";\n\n        const size_t          count = g_pPluginSystem->pluginCount();\n        std::vector<CPlugin*> plugins(count);\n        g_pPluginSystem->sigGetPlugins(plugins.data(), count);\n\n        for (size_t i = 0; i < count; i++) {\n            auto p = plugins[i];\n            finalCrashReport += '\\t';\n            finalCrashReport += p->m_name;\n            finalCrashReport += \" (\";\n            finalCrashReport += p->m_author;\n            finalCrashReport += \") \";\n            finalCrashReport += p->m_version;\n            finalCrashReport += '\\n';\n        }\n\n        finalCrashReport += \"\\n\\n\";\n    }\n\n    finalCrashReport += \"System info:\\n\";\n\n    {\n        struct utsname unameInfo;\n        uname(&unameInfo);\n\n        finalCrashReport += \"\\tSystem name: \";\n        finalCrashReport += unameInfo.sysname;\n        finalCrashReport += \"\\n\\tNode name: \";\n        finalCrashReport += unameInfo.nodename;\n        finalCrashReport += \"\\n\\tRelease: \";\n        finalCrashReport += unameInfo.release;\n        finalCrashReport += \"\\n\\tVersion: \";\n        finalCrashReport += unameInfo.version;\n        finalCrashReport += \"\\n\\n\";\n    }\n\n    finalCrashReport += \"GPU:\\n\\t\";\n#if defined(__DragonFly__) || defined(__FreeBSD__)\n    finalCrashReport.writeCmdOutput(\"pciconf -lv | grep -F -A4 vga\");\n#else\n    finalCrashReport.writeCmdOutput(\"lspci -vnn | grep -E '(VGA|Display|3D)'\");\n#endif\n\n    finalCrashReport += \"\\n\\nos-release:\\n\";\n    finalCrashReport.writeCmdOutput(\"cat /etc/os-release | sed 's/^/\\t/'\");\n\n    finalCrashReport += '\\n';\n    finalCrashReport += getBuiltSystemLibraryNames();\n    finalCrashReport += '\\n';\n\n    // dladdr1()/backtrace_symbols()/this entire section allocates, and hence is NOT async-signal-safe.\n    // Make sure that we save the current known crash report information,\n    // so that if we are caught in a deadlock during a call to malloc(),\n    // there is still something to debug from.\n    finalCrashReport.flush();\n\n    finalCrashReport += \"Backtrace:\\n\";\n\n    const auto CALLSTACK = getBacktrace();\n\n#if defined(KERN_PROC_PATHNAME)\n    int mib[] = {\n        CTL_KERN,\n#if defined(__NetBSD__)\n        KERN_PROC_ARGS,\n        -1,\n        KERN_PROC_PATHNAME,\n#else\n        KERN_PROC,\n        KERN_PROC_PATHNAME,\n        -1,\n#endif\n    };\n    u_int  miblen        = sizeof(mib) / sizeof(mib[0]);\n    char   exe[PATH_MAX] = \"/nonexistent\";\n    size_t sz            = sizeof(exe);\n    sysctl(mib, miblen, &exe, &sz, NULL, 0);\n    const auto FPATH = std::filesystem::canonical(exe);\n#elif defined(__OpenBSD__)\n    // Neither KERN_PROC_PATHNAME nor /proc are supported\n    const auto FPATH = std::filesystem::canonical(\"/usr/local/bin/Hyprland\");\n#else\n    const auto FPATH = std::filesystem::canonical(\"/proc/self/exe\");\n#endif\n\n    std::string addrs = \"\";\n    for (size_t i = 0; i < CALLSTACK.size(); ++i) {\n#ifdef __GLIBC__\n        // convert in memory address to VMA address\n        Dl_info          info;\n        struct link_map* linkMap;\n        dladdr1(CALLSTACK[i].adr, &info, rc<void**>(&linkMap), RTLD_DL_LINKMAP);\n        size_t vmaAddr = rc<size_t>(CALLSTACK[i].adr) - linkMap->l_addr;\n#else\n        // musl doesn't define dladdr1\n        size_t vmaAddr = (size_t)CALLSTACK[i].adr;\n#endif\n\n        addrs += std::format(\"0x{:x} \", vmaAddr);\n    }\n#ifdef __clang__\n    const auto CMD = std::format(\"llvm-addr2line -e {} -Cf {}\", FPATH.c_str(), addrs);\n#else\n    const auto CMD = std::format(\"addr2line -e {} -Cf {}\", FPATH.c_str(), addrs);\n#endif\n\n    const auto        ADDR2LINE = execAndGet(CMD.c_str());\n\n    std::stringstream ssin(ADDR2LINE);\n\n    for (size_t i = 0; i < CALLSTACK.size(); ++i) {\n        finalCrashReport += \"\\t#\";\n        finalCrashReport.writeNum(i);\n        finalCrashReport += \" | \";\n        finalCrashReport += CALLSTACK[i].desc;\n        std::string functionInfo;\n        std::string fileLineInfo;\n        std::getline(ssin, functionInfo);\n        std::getline(ssin, fileLineInfo);\n        finalCrashReport += std::format(\"\\n\\t\\t{}\\n\\t\\t{}\\n\", functionInfo, fileLineInfo);\n    }\n\n    finalCrashReport += \"\\n\\nLog tail:\\n\";\n\n    finalCrashReport += Log::logger->rolling();\n}\n"
  },
  {
    "path": "src/debug/crash/CrashReporter.hpp",
    "content": "#pragma once\n\nnamespace CrashReporter {\n    void createAndSaveCrash(int sig);\n};"
  },
  {
    "path": "src/debug/crash/SignalSafe.cpp",
    "content": "#include \"SignalSafe.hpp\"\n\n#ifndef __GLIBC__\n#include <signal.h>\n#endif\n#include <fcntl.h>\n#include <unistd.h>\n#include <cstring>\n\nusing namespace SignalSafe;\n\n// NOLINTNEXTLINE\nextern \"C\" char** environ;\n\n//\nchar const* SignalSafe::getenv(char const* name) {\n    const size_t len = strlen(name);\n    for (char** var = environ; *var != nullptr; var++) {\n        if (strncmp(*var, name, len) == 0 && (*var)[len] == '=') {\n            return (*var) + len + 1;\n        }\n    }\n    return nullptr;\n}\n\nchar const* SignalSafe::strsignal(int sig) {\n#ifdef __GLIBC__\n    return sigabbrev_np(sig);\n#elif defined(__DragonFly__) || defined(__FreeBSD__)\n    return sys_signame[sig];\n#else\n    return \"unknown\";\n#endif\n}\n"
  },
  {
    "path": "src/debug/crash/SignalSafe.hpp",
    "content": "#pragma once\n\n#include \"defines.hpp\"\n#include <cstring>\n\nnamespace SignalSafe {\n    template <uint16_t N>\n    class CMaxLengthCString {\n      public:\n        CMaxLengthCString() {\n            m_str[0] = '\\0';\n        }\n\n        void operator+=(char const* rhs) {\n            write(rhs, strlen(rhs));\n        }\n\n        void write(char const* data, size_t len) {\n            if (m_boundsExceeded || m_strPos + len >= N) {\n                m_boundsExceeded = true;\n                return;\n            }\n            memcpy(m_str + m_strPos, data, len);\n            m_strPos += len;\n            m_str[m_strPos] = '\\0';\n        }\n\n        void write(char c) {\n            if (m_boundsExceeded || m_strPos + 1 >= N) {\n                m_boundsExceeded = true;\n                return;\n            }\n            m_str[m_strPos] = c;\n            m_strPos++;\n        }\n\n        void writeNum(size_t num) {\n            size_t d = 1;\n\n            while (num / 10 >= d) {\n                d *= 10;\n            }\n\n            while (num > 0) {\n                char c = '0' + (num / d);\n                write(c);\n                num %= d;\n                d /= 10;\n            }\n        }\n\n        char const* getStr() {\n            return m_str;\n        }\n\n        bool boundsExceeded() {\n            return m_boundsExceeded;\n        }\n\n      private:\n        char   m_str[N];\n        size_t m_strPos         = 0;\n        bool   m_boundsExceeded = false;\n    };\n\n    template <uint16_t BUFSIZE>\n    class CBufFileWriter {\n      public:\n        CBufFileWriter(int fd_) : m_fd(fd_) {\n            ;\n        }\n\n        ~CBufFileWriter() {\n            flush();\n        }\n\n        void write(char const* data, size_t len) {\n            while (len > 0) {\n                size_t to_add = std::min(len, sc<size_t>(BUFSIZE) - m_writeBufPos);\n                memcpy(m_writeBuf + m_writeBufPos, data, to_add);\n                data += to_add;\n                len -= to_add;\n                m_writeBufPos += to_add;\n                if (m_writeBufPos == BUFSIZE)\n                    flush();\n            }\n        }\n\n        void write(char c) {\n            if (m_writeBufPos == BUFSIZE)\n                flush();\n            m_writeBuf[m_writeBufPos] = c;\n            m_writeBufPos++;\n        }\n\n        void operator+=(char const* str) {\n            write(str, strlen(str));\n        }\n\n        void operator+=(std::string_view str) {\n            write(str.data(), str.size());\n        }\n\n        void operator+=(char c) {\n            write(c);\n        }\n\n        void writeNum(size_t num) {\n            size_t d = 1;\n\n            while (num / 10 >= d) {\n                d *= 10;\n            }\n\n            while (num > 0) {\n                char c = '0' + (num / d);\n                write(c);\n                num %= d;\n                d /= 10;\n            }\n        }\n\n        void writeCmdOutput(const char* cmd) {\n            int pipefd[2];\n            if (pipe(pipefd) < 0) {\n                *this += \"<pipe(pipefd) failed with\";\n                writeNum(errno);\n                *this += \">\\n\";\n                return;\n            }\n\n            // terminate child instead of waiting\n            {\n                struct sigaction act;\n                act.sa_handler = SIG_DFL;\n                sigemptyset(&act.sa_mask);\n                act.sa_flags = SA_NOCLDWAIT;\n#ifdef SA_RESTORER\n                act.sa_restorer = NULL;\n#endif\n                sigaction(SIGCHLD, &act, nullptr);\n            }\n\n            const pid_t pid = fork();\n\n            if (pid < 0) {\n                *this += \"<fork() failed with \";\n                writeNum(errno);\n                *this += \">\\n\";\n                return;\n            }\n\n            if (pid == 0) {\n                close(pipefd[0]);\n                dup2(pipefd[1], STDOUT_FILENO);\n                char const* const argv[] = {\"/bin/sh\", \"-c\", cmd, nullptr};\n                execv(\"/bin/sh\", cc<char* const*>(argv));\n\n                CBufFileWriter<64> failmsg(pipefd[1]);\n                failmsg += \"<execv(\";\n                failmsg += cmd;\n                failmsg += \") resulted in errno \";\n                failmsg.write(errno);\n                failmsg += \">\\n\";\n                close(pipefd[1]);\n                abort();\n            } else {\n                close(pipefd[1]);\n                int64_t len = 0;\n                char    readbuf[256];\n                while ((len = read(pipefd[0], readbuf, 256)) > 0) {\n                    write(readbuf, len);\n                }\n                if (len < 0) {\n                    *this += \"<interrupted, read() resulted in errno \";\n                    writeNum(errno);\n                    *this += \">\\n\";\n                }\n                close(pipefd[0]);\n            }\n        }\n\n        void flush() {\n            size_t i = 0;\n            while (i < m_writeBufPos) {\n                auto written = ::write(m_fd, m_writeBuf + i, m_writeBufPos - i);\n                if (written <= 0) {\n                    return;\n                }\n                i += written;\n            }\n            m_writeBufPos = 0;\n        }\n\n      private:\n        char   m_writeBuf[BUFSIZE] = {0};\n        size_t m_writeBufPos       = 0;\n        int    m_fd                = 0;\n    };\n\n    char const* getenv(const char* name);\n    char const* strsignal(int sig);\n}\n"
  },
  {
    "path": "src/debug/log/Logger.cpp",
    "content": "#include \"Logger.hpp\"\n#include \"RollingLogFollow.hpp\"\n\n#include \"../../event/EventBus.hpp\"\n\n#include \"../../config/ConfigValue.hpp\"\n\nusing namespace Log;\n\nCLogger::CLogger() {\n    const auto IS_TRACE = Env::isTrace();\n    m_logger.setLogLevel(IS_TRACE ? Hyprutils::CLI::LOG_TRACE : Hyprutils::CLI::LOG_DEBUG);\n}\n\nvoid CLogger::log(Hyprutils::CLI::eLogLevel level, const std::string_view& str) {\n\n    static bool TRACE = Env::isTrace();\n\n    if (!m_logsEnabled)\n        return;\n\n    if (level == Hyprutils::CLI::LOG_TRACE && !TRACE)\n        return;\n\n    if (SRollingLogFollow::get().isRunning())\n        SRollingLogFollow::get().addLog(str);\n\n    m_logger.log(level, str);\n}\n\nvoid CLogger::initIS(const std::string_view& IS) {\n    // NOLINTNEXTLINE\n    m_logger.setOutputFile(std::string{IS} + (ISDEBUG ? \"/hyprlandd.log\" : \"/hyprland.log\"));\n    m_logger.setEnableRolling(true);\n    m_logger.setEnableColor(false);\n    m_logger.setEnableStdout(true);\n    m_logger.setTime(false);\n}\n\nvoid CLogger::initCallbacks() {\n    static auto P = Event::bus()->m_events.config.reloaded.listen([this]() { recheckCfg(); });\n    recheckCfg();\n}\n\nvoid CLogger::recheckCfg() {\n    static auto PDISABLELOGS  = CConfigValue<Hyprlang::INT>(\"debug:disable_logs\");\n    static auto PDISABLETIME  = CConfigValue<Hyprlang::INT>(\"debug:disable_time\");\n    static auto PENABLESTDOUT = CConfigValue<Hyprlang::INT>(\"debug:enable_stdout_logs\");\n    static auto PENABLECOLOR  = CConfigValue<Hyprlang::INT>(\"debug:colored_stdout_logs\");\n\n    m_logger.setEnableStdout(!*PDISABLELOGS && *PENABLESTDOUT);\n    m_logsEnabled = !*PDISABLELOGS;\n    m_logger.setTime(!*PDISABLETIME);\n    m_logger.setEnableColor(*PENABLECOLOR);\n}\n\nconst std::string& CLogger::rolling() {\n    return m_logger.rollingLog();\n}\n\nHyprutils::CLI::CLogger& CLogger::hu() {\n    return m_logger;\n}\n"
  },
  {
    "path": "src/debug/log/Logger.hpp",
    "content": "#pragma once\n\n#include <hyprutils/cli/Logger.hpp>\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/env/Env.hpp\"\n\nnamespace Log {\n    class CLogger {\n      public:\n        CLogger();\n        ~CLogger() = default;\n\n        void initIS(const std::string_view& IS);\n        void initCallbacks();\n\n        void log(Hyprutils::CLI::eLogLevel level, const std::string_view& str);\n\n        template <typename... Args>\n        //NOLINTNEXTLINE\n        void log(Hyprutils::CLI::eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {\n            static bool TRACE = Env::isTrace();\n\n            if (!m_logsEnabled)\n                return;\n\n            if (level == Hyprutils::CLI::LOG_TRACE && !TRACE)\n                return;\n\n            std::string logMsg = \"\";\n\n            // no need for try {} catch {} because std::format_string<Args...> ensures that vformat never throw std::format_error\n            // because\n            // 1. any faulty format specifier that sucks will cause a compilation error.\n            // 2. and `std::bad_alloc` is catastrophic, (Almost any operation in stdlib could throw this.)\n            // 3. this is actually what std::format in stdlib does\n            logMsg += std::vformat(fmt.get(), std::make_format_args(args...));\n\n            log(level, logMsg);\n        }\n\n        const std::string&       rolling();\n        Hyprutils::CLI::CLogger& hu();\n\n      private:\n        void                    recheckCfg();\n\n        Hyprutils::CLI::CLogger m_logger;\n        bool                    m_logsEnabled = true;\n    };\n\n    inline UP<CLogger> logger = makeUnique<CLogger>();\n\n    //\n    inline constexpr const Hyprutils::CLI::eLogLevel DEBUG = Hyprutils::CLI::LOG_DEBUG;\n    inline constexpr const Hyprutils::CLI::eLogLevel WARN  = Hyprutils::CLI::LOG_WARN;\n    inline constexpr const Hyprutils::CLI::eLogLevel ERR   = Hyprutils::CLI::LOG_ERR;\n    inline constexpr const Hyprutils::CLI::eLogLevel CRIT  = Hyprutils::CLI::LOG_CRIT;\n    inline constexpr const Hyprutils::CLI::eLogLevel INFO  = Hyprutils::CLI::LOG_DEBUG;\n    inline constexpr const Hyprutils::CLI::eLogLevel TRACE = Hyprutils::CLI::LOG_TRACE;\n};\n"
  },
  {
    "path": "src/debug/log/RollingLogFollow.hpp",
    "content": "#pragma once\n\n#include <shared_mutex>\n#include <unordered_map>\n#include <format>\n#include <vector>\n\nnamespace Log {\n    struct SRollingLogFollow {\n        std::unordered_map<int, std::string> m_socketToRollingLogFollowQueue;\n        std::shared_mutex                    m_mutex;\n        bool                                 m_running                  = false;\n        static constexpr size_t              ROLLING_LOG_FOLLOW_TOO_BIG = 8192;\n\n        // Returns true if the queue is empty for the given socket\n        bool isEmpty(int socket) {\n            std::shared_lock<std::shared_mutex> r(m_mutex);\n            return m_socketToRollingLogFollowQueue[socket].empty();\n        }\n\n        std::string debugInfo() {\n            std::shared_lock<std::shared_mutex> r(m_mutex);\n            return std::format(\"RollingLogFollow, got {} connections\", m_socketToRollingLogFollowQueue.size());\n        }\n\n        std::string getLog(int socket) {\n            std::unique_lock<std::shared_mutex> w(m_mutex);\n\n            const std::string                   ret = m_socketToRollingLogFollowQueue[socket];\n            m_socketToRollingLogFollowQueue[socket] = \"\";\n\n            return ret;\n        };\n\n        void addLog(const std::string_view& log) {\n            std::unique_lock<std::shared_mutex> w(m_mutex);\n            m_running = true;\n            std::vector<int> to_erase;\n            for (const auto& p : m_socketToRollingLogFollowQueue) {\n                m_socketToRollingLogFollowQueue[p.first] += log;\n                m_socketToRollingLogFollowQueue[p.first] += \"\\n\";\n            }\n        }\n\n        bool isRunning() {\n            std::shared_lock<std::shared_mutex> r(m_mutex);\n            return m_running;\n        }\n\n        void stopFor(int socket) {\n            std::unique_lock<std::shared_mutex> w(m_mutex);\n            m_socketToRollingLogFollowQueue.erase(socket);\n            if (m_socketToRollingLogFollowQueue.empty())\n                m_running = false;\n        }\n\n        void startFor(int socket) {\n            std::unique_lock<std::shared_mutex> w(m_mutex);\n            m_socketToRollingLogFollowQueue[socket] = std::format(\"[LOG] Following log to socket: {} started\\n\", socket);\n            m_running                               = true;\n        }\n\n        static SRollingLogFollow& get() {\n            static SRollingLogFollow    instance;\n            static std::mutex           gm;\n            std::lock_guard<std::mutex> lock(gm);\n            return instance;\n        };\n    };\n}\n"
  },
  {
    "path": "src/defines.hpp",
    "content": "#pragma once\n\n#include \"includes.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"helpers/Color.hpp\"\n#include \"macros.hpp\"\n#include \"desktop/DesktopTypes.hpp\"\n\n#if !defined(__GXX_RTTI)\n#error \"Hyprland requires C++ RTTI. Shit will hit the fan otherwise. Do not even try.\"\n#endif\n"
  },
  {
    "path": "src/desktop/DesktopTypes.hpp",
    "content": "#pragma once\n#include \"../helpers/memory/Memory.hpp\"\n\nclass CWorkspace;\nclass CMonitor;\n\nnamespace Desktop::View {\n    class CWindow;\n    class CLayerSurface;\n}\n\n/* Shared pointer to a workspace */\nusing PHLWORKSPACE = SP<CWorkspace>;\n/* Weak pointer to a workspace */\nusing PHLWORKSPACEREF = WP<CWorkspace>;\n\n/* Shared pointer to a window */\nusing PHLWINDOW = SP<Desktop::View::CWindow>;\n/* Weak pointer to a window */\nusing PHLWINDOWREF = WP<Desktop::View::CWindow>;\n\n/* Shared pointer to a layer surface */\nusing PHLLS = SP<Desktop::View::CLayerSurface>;\n/* Weak pointer to a layer surface */\nusing PHLLSREF = WP<Desktop::View::CLayerSurface>;\n\n/* Shared pointer to a monitor */\nusing PHLMONITOR = SP<CMonitor>;\n/* Weak pointer to a monitor */\nusing PHLMONITORREF = WP<CMonitor>;"
  },
  {
    "path": "src/desktop/Workspace.cpp",
    "content": "#include \"Workspace.hpp\"\n#include \"view/Group.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"config/ConfigManager.hpp\"\n#include \"managers/animation/AnimationManager.hpp\"\n#include \"../managers/EventManager.hpp\"\n#include \"../layout/space/Space.hpp\"\n#include \"../layout/supplementary/WorkspaceAlgoMatcher.hpp\"\n#include \"../event/EventBus.hpp\"\n\n#include <hyprutils/animation/AnimatedVariable.hpp>\n#include <hyprutils/string/String.hpp>\nusing namespace Hyprutils::String;\n\nPHLWORKSPACE CWorkspace::create(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special, bool isEmpty) {\n    PHLWORKSPACE workspace = makeShared<CWorkspace>(id, monitor, name, special, isEmpty);\n    workspace->init(workspace);\n    g_pCompositor->registerWorkspace(workspace);\n    return workspace;\n}\n\nCWorkspace::CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special, bool isEmpty) :\n    m_id(id), m_name(name), m_monitor(monitor), m_isSpecialWorkspace(special), m_wasCreatedEmpty(isEmpty) {\n    ;\n}\n\nvoid CWorkspace::init(PHLWORKSPACE self) {\n    m_self = self;\n\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), m_renderOffset, g_pConfigManager->getAnimationPropertyConfig(m_isSpecialWorkspace ? \"specialWorkspaceIn\" : \"workspacesIn\"),\n                                         self, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(1.f, m_alpha, g_pConfigManager->getAnimationPropertyConfig(m_isSpecialWorkspace ? \"specialWorkspaceIn\" : \"workspacesIn\"), self,\n                                         AVARDAMAGE_ENTIRE);\n\n    const auto RULEFORTHIS = g_pConfigManager->getWorkspaceRuleFor(self);\n    if (RULEFORTHIS.defaultName.has_value())\n        m_name = RULEFORTHIS.defaultName.value();\n    if (RULEFORTHIS.animationStyle.has_value())\n        m_animationStyle = RULEFORTHIS.animationStyle.value();\n\n    m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) {\n        if (pWindow == m_lastFocusedWindow.lock())\n            m_lastFocusedWindow.reset();\n    });\n\n    m_space = Layout::CSpace::create(m_self.lock());\n    m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock()));\n\n    m_inert = false;\n\n    const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self);\n    setPersistent(WORKSPACERULE.isPersistent);\n\n    if (self->m_wasCreatedEmpty)\n        if (auto cmd = WORKSPACERULE.onCreatedEmptyRunCmd)\n            CKeybindManager::spawnWithRules(*cmd, self);\n\n    g_pEventManager->postEvent({.event = \"createworkspace\", .data = m_name});\n    g_pEventManager->postEvent({.event = \"createworkspacev2\", .data = std::format(\"{},{}\", m_id, m_name)});\n    Event::bus()->m_events.workspace.created.emit(self);\n}\n\nCWorkspace::~CWorkspace() {\n    Log::logger->log(Log::DEBUG, \"Destroying workspace ID {}\", m_id);\n\n    if (g_pEventManager) {\n        g_pEventManager->postEvent({.event = \"destroyworkspace\", .data = m_name});\n        g_pEventManager->postEvent({.event = \"destroyworkspacev2\", .data = std::format(\"{},{}\", m_id, m_name)});\n    }\n\n    Event::bus()->m_events.workspace.removed.emit(m_self);\n\n    m_events.destroy.emit();\n}\n\nPHLWINDOW CWorkspace::getLastFocusedWindow() {\n    if (!validMapped(m_lastFocusedWindow) || m_lastFocusedWindow->workspaceID() != m_id)\n        return nullptr;\n\n    return m_lastFocusedWindow.lock();\n}\n\nstd::string CWorkspace::getConfigName() {\n    if (m_isSpecialWorkspace) {\n        return m_name;\n    }\n\n    if (m_id > 0)\n        return std::to_string(m_id);\n\n    return \"name:\" + m_name;\n}\n\nbool CWorkspace::matchesStaticSelector(const std::string& selector_) {\n    auto selector = trim(selector_);\n\n    if (selector.empty())\n        return true;\n\n    if (isNumber(selector)) {\n        const auto& [wsid, wsname, isAutoID] = getWorkspaceIDNameFromString(selector);\n\n        if (wsid == WORKSPACE_INVALID)\n            return false;\n\n        return wsid == m_id;\n\n    } else if (selector.starts_with(\"name:\")) {\n        return m_name == selector.substr(5);\n    } else if (selector.starts_with(\"special\")) {\n        return m_name == selector;\n    } else {\n        // parse selector\n\n        for (size_t i = 0; i < selector.length(); ++i) {\n            const char& cur = selector[i];\n            if (std::isspace(cur))\n                continue;\n\n            // Allowed selectors:\n            // r - range: r[1-5]\n            // s - special: s[true]\n            // n - named: n[true] or n[s:string] or n[e:string]\n            // m - monitor: m[monitor_selector]\n            // w - windowCount: w[1-4] or w[1], optional flag t or f for tiled or floating and\n            //                  flag p to count only pinned windows, e.g. w[p1-2], w[pg4]\n            //                  flag g to count groups instead of windows, e.g. w[t1-2], w[fg4]\n            //                  flag v will count only visible windows\n            // f - fullscreen state : f[-1], f[0], f[1], or f[2] for different fullscreen states\n            //                        -1: no fullscreen, 0: fullscreen, 1: maximized, 2: fullscreen without sending fs state to window\n\n            const auto  CLOSING_BRACKET = selector.find_first_of(']', i);\n            std::string prop            = selector.substr(i, CLOSING_BRACKET == std::string::npos ? std::string::npos : CLOSING_BRACKET + 1 - i);\n            i                           = std::min(CLOSING_BRACKET, std::string::npos - 1);\n\n            if (cur == 'r') {\n                WORKSPACEID from = 0, to = 0;\n                if (!prop.starts_with(\"r[\") || !prop.ends_with(\"]\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                prop = prop.substr(2, prop.length() - 3);\n\n                if (!prop.contains(\"-\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                const auto DASHPOS = prop.find('-');\n                const auto LHS = prop.substr(0, DASHPOS), RHS = prop.substr(DASHPOS + 1);\n\n                if (!isNumber(LHS) || !isNumber(RHS)) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                try {\n                    from = std::stoll(LHS);\n                    to   = std::stoll(RHS);\n                } catch (std::exception& e) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                if (to < from || to < 1 || from < 1) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                if (std::clamp(m_id, from, to) != m_id)\n                    return false;\n                continue;\n            }\n\n            if (cur == 's') {\n                if (!prop.starts_with(\"s[\") || !prop.ends_with(\"]\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                prop = prop.substr(2, prop.length() - 3);\n\n                const auto SHOULDBESPECIAL = configStringToInt(prop);\n\n                if (SHOULDBESPECIAL && sc<bool>(*SHOULDBESPECIAL) != m_isSpecialWorkspace)\n                    return false;\n                continue;\n            }\n\n            if (cur == 'm') {\n                if (!prop.starts_with(\"m[\") || !prop.ends_with(\"]\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                prop = prop.substr(2, prop.length() - 3);\n\n                const auto PMONITOR = g_pCompositor->getMonitorFromString(prop);\n\n                if (!(PMONITOR ? PMONITOR == m_monitor : false))\n                    return false;\n                continue;\n            }\n\n            if (cur == 'n') {\n                if (!prop.starts_with(\"n[\") || !prop.ends_with(\"]\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                prop = prop.substr(2, prop.length() - 3);\n\n                if (prop.starts_with(\"s:\") && !m_name.starts_with(prop.substr(2)))\n                    return false;\n                if (prop.starts_with(\"e:\") && !m_name.ends_with(prop.substr(2)))\n                    return false;\n\n                const auto WANTSNAMED = configStringToInt(prop);\n\n                if (WANTSNAMED && *WANTSNAMED != (m_id <= -1337))\n                    return false;\n                continue;\n            }\n\n            if (cur == 'w') {\n                WORKSPACEID from = 0, to = 0;\n                if (!prop.starts_with(\"w[\") || !prop.ends_with(\"]\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                prop = prop.substr(2, prop.length() - 3);\n\n                int  wantsOnlyTiled    = -1;\n                int  wantsOnlyPinned   = false;\n                bool wantsCountGroup   = false;\n                bool wantsCountVisible = false;\n\n                int  flagCount = 0;\n                for (auto const& flag : prop) {\n                    if (flag == 't' && wantsOnlyTiled == -1) {\n                        wantsOnlyTiled = 1;\n                        flagCount++;\n                    } else if (flag == 'f' && wantsOnlyTiled == -1) {\n                        wantsOnlyTiled = 0;\n                        flagCount++;\n                    } else if (flag == 'p' && !wantsOnlyPinned) {\n                        wantsOnlyPinned = true;\n                        flagCount++;\n                    } else if (flag == 'g' && !wantsCountGroup) {\n                        wantsCountGroup = true;\n                        flagCount++;\n                    } else if (flag == 'v' && !wantsCountVisible) {\n                        wantsCountVisible = true;\n                        flagCount++;\n                    } else {\n                        break;\n                    }\n                }\n                prop = prop.substr(flagCount);\n\n                if (!prop.contains(\"-\")) {\n                    // try single\n\n                    if (!isNumber(prop)) {\n                        Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                        return false;\n                    }\n\n                    try {\n                        from = std::stoll(prop);\n                    } catch (std::exception& e) {\n                        Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                        return false;\n                    }\n\n                    int count;\n                    if (wantsCountGroup)\n                        count = getGroups(wantsOnlyTiled == -1 ? std::nullopt : std::optional<bool>(sc<bool>(wantsOnlyTiled)),\n                                          wantsOnlyPinned ? std::optional<bool>(wantsOnlyPinned) : std::nullopt,\n                                          wantsCountVisible ? std::optional<bool>(wantsCountVisible) : std::nullopt);\n                    else\n                        count = getWindows(wantsOnlyTiled == -1 ? std::nullopt : std::optional<bool>(sc<bool>(wantsOnlyTiled)),\n                                           wantsOnlyPinned ? std::optional<bool>(wantsOnlyPinned) : std::nullopt,\n                                           wantsCountVisible ? std::optional<bool>(wantsCountVisible) : std::nullopt);\n\n                    if (count != from)\n                        return false;\n                    continue;\n                }\n\n                const auto DASHPOS = prop.find('-');\n                const auto LHS = prop.substr(0, DASHPOS), RHS = prop.substr(DASHPOS + 1);\n\n                if (!isNumber(LHS) || !isNumber(RHS)) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                try {\n                    from = std::stoll(LHS);\n                    to   = std::stoll(RHS);\n                } catch (std::exception& e) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                if (to < from || to < 1 || from < 1) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                WORKSPACEID count;\n                if (wantsCountGroup)\n                    count =\n                        getGroups(wantsOnlyTiled == -1 ? std::nullopt : std::optional<bool>(sc<bool>(wantsOnlyTiled)),\n                                  wantsOnlyPinned ? std::optional<bool>(wantsOnlyPinned) : std::nullopt, wantsCountVisible ? std::optional<bool>(wantsCountVisible) : std::nullopt);\n                else\n                    count = getWindows(wantsOnlyTiled == -1 ? std::nullopt : std::optional<bool>(sc<bool>(wantsOnlyTiled)),\n                                       wantsOnlyPinned ? std::optional<bool>(wantsOnlyPinned) : std::nullopt,\n                                       wantsCountVisible ? std::optional<bool>(wantsCountVisible) : std::nullopt);\n\n                if (std::clamp(count, from, to) != count)\n                    return false;\n                continue;\n            }\n\n            if (cur == 'f') {\n                if (!prop.starts_with(\"f[\") || !prop.ends_with(\"]\")) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                prop        = prop.substr(2, prop.length() - 3);\n                int FSSTATE = -1;\n                try {\n                    FSSTATE = std::stoi(prop);\n                } catch (std::exception& e) {\n                    Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n                    return false;\n                }\n\n                switch (FSSTATE) {\n                    case -1: // no fullscreen\n                        if (m_hasFullscreenWindow)\n                            return false;\n                        break;\n                    case 0: // fullscreen full\n                        if (!m_hasFullscreenWindow || m_fullscreenMode != FSMODE_FULLSCREEN)\n                            return false;\n                        break;\n                    case 1: // maximized\n                        if (!m_hasFullscreenWindow || m_fullscreenMode != FSMODE_MAXIMIZED)\n                            return false;\n                        break;\n                    default: break;\n                }\n                continue;\n            }\n\n            Log::logger->log(Log::DEBUG, \"Invalid selector {}\", selector);\n            return false;\n        }\n\n        return true;\n    }\n\n    UNREACHABLE();\n    return false;\n}\n\nvoid CWorkspace::markInert() {\n    m_inert   = true;\n    m_id      = WORKSPACE_INVALID;\n    m_visible = false;\n    m_monitor.reset();\n}\n\nbool CWorkspace::inert() {\n    return m_inert;\n}\n\nMONITORID CWorkspace::monitorID() {\n    return m_monitor ? m_monitor->m_id : MONITOR_INVALID;\n}\n\nPHLWINDOW CWorkspace::getFullscreenWindow() {\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace == m_self && w->isFullscreen())\n            return w;\n    }\n\n    return nullptr;\n}\n\nbool CWorkspace::isVisible() {\n    return m_visible;\n}\n\nbool CWorkspace::isVisibleNotCovered() {\n    const auto PMONITOR = m_monitor.lock();\n    if (PMONITOR->m_activeSpecialWorkspace)\n        return PMONITOR->m_activeSpecialWorkspace->m_id == m_id;\n\n    return PMONITOR->m_activeWorkspace->m_id == m_id;\n}\n\nint CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {\n    int no = 0;\n\n    if (!m_space)\n        return 0;\n\n    for (auto const& t : m_space->targets()) {\n        if (!t)\n            continue;\n\n        if (onlyTiled.has_value() && t->floating() == onlyTiled.value())\n            continue;\n        if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value()))\n            continue;\n        if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value()))\n            continue;\n        no++;\n    }\n\n    return no;\n}\n\nint CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {\n    int no = 0;\n    for (auto const& g : Desktop::View::groups()) {\n        const auto HEAD = g->head();\n\n        if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped)\n            continue;\n        if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value())\n            continue;\n        if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value())\n            continue;\n        if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value())\n            continue;\n        no++;\n    }\n    return no;\n}\n\nPHLWINDOW CWorkspace::getFirstWindow() {\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace == m_self && w->m_isMapped && !w->isHidden())\n            return w;\n    }\n\n    return nullptr;\n}\n\nPHLWINDOW CWorkspace::getTopLeftWindow() {\n    const auto PMONITOR = m_monitor.lock();\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace != m_self || !w->m_isMapped || w->isHidden())\n            continue;\n\n        const auto WINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved();\n\n        if (WINDOWIDEALBB.x <= PMONITOR->m_position.x + 1 && WINDOWIDEALBB.y <= PMONITOR->m_position.y + 1)\n            return w;\n    }\n    return nullptr;\n}\n\nbool CWorkspace::hasUrgentWindow() {\n    return std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_workspace == m_self && w->m_isMapped && w->m_isUrgent; });\n}\n\nvoid CWorkspace::updateWindowDecos() {\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace != m_self)\n            continue;\n\n        w->updateWindowDecos();\n    }\n}\n\nvoid CWorkspace::updateWindowData() {\n    const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_self.lock());\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace != m_self)\n            continue;\n\n        w->updateWindowData(WORKSPACERULE);\n    }\n}\n\nvoid CWorkspace::forceReportSizesToWindows() {\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace != m_self || !w->m_isMapped || w->isHidden())\n            continue;\n\n        w->sendWindowSize(true);\n    }\n}\n\nvoid CWorkspace::rename(const std::string& name) {\n    if (g_pCompositor->isWorkspaceSpecial(m_id))\n        return;\n\n    Log::logger->log(Log::DEBUG, \"CWorkspace::rename: Renaming workspace {} to '{}'\", m_id, name);\n    m_name = name;\n\n    const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_self.lock());\n    setPersistent(WORKSPACERULE.isPersistent);\n\n    if (WORKSPACERULE.isPersistent)\n        g_pCompositor->ensurePersistentWorkspacesPresent(std::vector<SWorkspaceRule>{WORKSPACERULE}, m_self.lock());\n\n    g_pEventManager->postEvent({.event = \"renameworkspace\", .data = std::to_string(m_id) + \",\" + m_name});\n    m_events.renamed.emit();\n}\n\nvoid CWorkspace::updateWindows() {\n    m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; });\n\n    for (auto const& t : m_space->targets()) {\n        if (t->window())\n            t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n    }\n}\n\nvoid CWorkspace::setPersistent(bool persistent) {\n    if (m_persistent == persistent)\n        return;\n\n    m_persistent = persistent;\n\n    if (persistent)\n        m_selfPersistent = m_self.lock();\n    else\n        m_selfPersistent.reset();\n}\n\nbool CWorkspace::isPersistent() {\n    return m_persistent;\n}\n"
  },
  {
    "path": "src/desktop/Workspace.hpp",
    "content": "#pragma once\n\n#include \"../helpers/AnimatedVariable.hpp\"\n#include <string>\n#include \"DesktopTypes.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nnamespace Layout {\n    class CSpace;\n};\n\nenum eFullscreenMode : int8_t {\n    FSMODE_NONE       = 0,\n    FSMODE_MAXIMIZED  = 1 << 0,\n    FSMODE_FULLSCREEN = 1 << 1,\n    FSMODE_MAX        = (1 << 2) - 1\n};\n\nclass CWorkspace {\n  public:\n    static PHLWORKSPACE create(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true);\n    // use create() don't use this\n    CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true);\n    ~CWorkspace();\n\n    WP<CWorkspace>     m_self;\n\n    SP<Layout::CSpace> m_space;\n\n    // Workspaces ID-based have IDs > 0\n    // and workspaces name-based have IDs starting with -1337\n    WORKSPACEID     m_id   = WORKSPACE_INVALID;\n    std::string     m_name = \"\";\n    PHLMONITORREF   m_monitor;\n\n    bool            m_hasFullscreenWindow = false;\n    eFullscreenMode m_fullscreenMode      = FSMODE_NONE;\n\n    wl_array        m_wlrCoordinateArr;\n\n    // for animations\n    PHLANIMVAR<Vector2D>       m_renderOffset;\n    PHLANIMVAR<float>          m_alpha;\n    bool                       m_forceRendering = false;\n    std::optional<std::string> m_animationStyle;\n\n    // allows damage to propagate.\n    bool m_visible = false;\n\n    // \"scratchpad\"\n    bool m_isSpecialWorkspace = false;\n\n    // last window\n    PHLWINDOWREF m_lastFocusedWindow;\n\n    // user-set\n    bool m_defaultFloating = false;\n    bool m_defaultPseudo   = false;\n\n    // last monitor (used on reconnect)\n    std::string m_lastMonitor = \"\";\n\n    bool        m_wasCreatedEmpty = true;\n\n    // Inert: destroyed and invalid. If this is true, release the ptr you have.\n    bool        inert();\n    MONITORID   monitorID();\n    PHLWINDOW   getLastFocusedWindow();\n    std::string getConfigName();\n    bool        matchesStaticSelector(const std::string& selector);\n    void        markInert();\n    void        updateWindowDecos();\n    void        updateWindowData();\n    int         getWindows(std::optional<bool> onlyTiled = {}, std::optional<bool> onlyPinned = {}, std::optional<bool> onlyVisible = {});\n    int         getGroups(std::optional<bool> onlyTiled = {}, std::optional<bool> onlyPinned = {}, std::optional<bool> onlyVisible = {});\n    bool        hasUrgentWindow();\n    PHLWINDOW   getFirstWindow();\n    PHLWINDOW   getTopLeftWindow();\n    PHLWINDOW   getFullscreenWindow();\n    bool        isVisible();\n    bool        isVisibleNotCovered();\n    void        rename(const std::string& name = \"\");\n    void        forceReportSizesToWindows();\n    void        updateWindows();\n    void        setPersistent(bool persistent);\n    bool        isPersistent();\n\n    struct {\n        CSignalT<> destroy;\n        CSignalT<> renamed;\n        CSignalT<> monitorChanged;\n        CSignalT<> activeChanged;\n    } m_events;\n\n  private:\n    void                init(PHLWORKSPACE self);\n\n    CHyprSignalListener m_focusedWindowHook;\n    bool                m_inert = true;\n\n    SP<CWorkspace>      m_selfPersistent; // for persistent workspaces.\n    bool                m_persistent = false;\n};\n\ninline bool valid(const PHLWORKSPACE& ref) {\n    if (!ref)\n        return false;\n\n    return !ref->inert();\n}\n"
  },
  {
    "path": "src/desktop/history/WindowHistoryTracker.cpp",
    "content": "#include \"WindowHistoryTracker.hpp\"\n\n#include \"../view/Window.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::History;\n\nSP<CWindowHistoryTracker> History::windowTracker() {\n    static SP<CWindowHistoryTracker> tracker = makeShared<CWindowHistoryTracker>();\n    return tracker;\n}\n\nCWindowHistoryTracker::CWindowHistoryTracker() {\n    static auto P = Event::bus()->m_events.window.openEarly.listen([this](PHLWINDOW pWindow) {\n        // add a last track\n        m_history.insert(m_history.begin(), pWindow);\n    });\n\n    static auto P1 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, uint8_t reason) { track(window); });\n}\n\nvoid CWindowHistoryTracker::track(PHLWINDOW w) {\n    std::erase(m_history, w);\n    m_history.emplace_back(w);\n}\n\nconst std::vector<PHLWINDOWREF>& CWindowHistoryTracker::fullHistory() {\n    gc();\n    return m_history;\n}\n\nstd::vector<PHLWINDOWREF> CWindowHistoryTracker::historyForWorkspace(PHLWORKSPACE ws) {\n    gc();\n    std::vector<PHLWINDOWREF> windows;\n\n    for (const auto& w : m_history) {\n        if (w->m_workspace != ws)\n            continue;\n\n        windows.emplace_back(w);\n    }\n\n    return windows;\n}\n\nvoid CWindowHistoryTracker::gc() {\n    std::erase_if(m_history, [](const auto& e) { return !e; });\n}\n"
  },
  {
    "path": "src/desktop/history/WindowHistoryTracker.hpp",
    "content": "#pragma once\n\n#include \"../DesktopTypes.hpp\"\n\n#include <vector>\n\nnamespace Desktop::History {\n    class CWindowHistoryTracker {\n      public:\n        CWindowHistoryTracker();\n        ~CWindowHistoryTracker() = default;\n\n        CWindowHistoryTracker(const CWindowHistoryTracker&) = delete;\n        CWindowHistoryTracker(CWindowHistoryTracker&)       = delete;\n        CWindowHistoryTracker(CWindowHistoryTracker&&)      = delete;\n\n        // History is ordered old -> new, meaning .front() is oldest, while .back() is newest\n\n        const std::vector<PHLWINDOWREF>& fullHistory();\n        std::vector<PHLWINDOWREF>        historyForWorkspace(PHLWORKSPACE ws);\n\n      private:\n        std::vector<PHLWINDOWREF> m_history;\n\n        void                      track(PHLWINDOW w);\n        void                      gc();\n    };\n\n    SP<CWindowHistoryTracker> windowTracker();\n};"
  },
  {
    "path": "src/desktop/history/WorkspaceHistoryTracker.cpp",
    "content": "#include \"WorkspaceHistoryTracker.hpp\"\n\n#include \"../../helpers/Monitor.hpp\"\n#include \"../Workspace.hpp\"\n#include \"../state/FocusState.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../event/EventBus.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Desktop;\nusing namespace Desktop::History;\n\nSP<CWorkspaceHistoryTracker> History::workspaceTracker() {\n    static SP<CWorkspaceHistoryTracker> tracker = makeShared<CWorkspaceHistoryTracker>();\n    return tracker;\n}\n\nCWorkspaceHistoryTracker::CWorkspaceHistoryTracker() {\n    static auto P = Event::bus()->m_events.workspace.active.listen([this](PHLWORKSPACE workspace) { track(workspace); });\n\n    static auto P1 = Event::bus()->m_events.monitor.focused.listen([this](PHLMONITOR mon) {\n        // This sucks ASS, but we have to do this because switching to a workspace on another mon will trigger a workspace event right afterwards and we don't\n        // want to remember the workspace that was not visible there\n        // TODO: do something about this\n        g_pEventLoopManager->doLater([this, mon = PHLMONITORREF{mon}] {\n            if (mon)\n                track(mon->m_activeWorkspace);\n        });\n    });\n}\n\nCWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::dataFor(PHLWORKSPACE ws) {\n    for (auto& ref : m_datas) {\n        if (ref.workspace != ws)\n            continue;\n\n        return ref;\n    }\n\n    return m_datas.emplace_back(SWorkspacePreviousData{\n        .workspace = ws,\n    });\n}\n\nvoid CWorkspaceHistoryTracker::track(PHLWORKSPACE w) {\n    if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace)\n        return;\n\n    static auto                   PALLOWWORKSPACECYCLES = CConfigValue<Hyprlang::INT>(\"binds:allow_workspace_cycles\");\n\n    auto&                         data = dataFor(w);\n\n    Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); });\n\n    if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES)\n        return;\n\n    data.previous = m_lastWorkspaceData.workspace;\n    if (m_lastWorkspaceData.workspace) {\n        data.previousName = m_lastWorkspaceData.workspace->m_name;\n        data.previousID   = m_lastWorkspaceData.workspace->m_id;\n        data.previousMon  = m_lastWorkspaceData.workspace->m_monitor;\n    } else {\n        data.previousName = m_lastWorkspaceData.workspaceName;\n        data.previousID   = m_lastWorkspaceData.workspaceID;\n        data.previousMon  = m_lastWorkspaceData.monitor;\n    }\n}\n\nvoid CWorkspaceHistoryTracker::gc() {\n    std::erase_if(m_datas, [](const auto& e) { return !e.workspace; });\n}\n\nconst CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) {\n    gc();\n\n    for (const auto& d : m_datas) {\n        if (d.workspace != ws)\n            continue;\n        return &d;\n    }\n\n    return &dataFor(ws);\n}\n\nSWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws) {\n    gc();\n\n    for (const auto& d : m_datas) {\n        if (d.workspace != ws)\n            continue;\n        return SWorkspaceIDName{.id = d.previousID, .name = d.previousName, .isAutoIDd = d.previousID <= 0};\n    }\n\n    auto& d = dataFor(ws);\n    return SWorkspaceIDName{.id = d.previousID, .name = d.previousName, .isAutoIDd = d.previousID <= 0};\n}\n\nconst CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict) {\n    if (!restrict)\n        return previousWorkspace(ws);\n\n    auto& data = dataFor(ws);\n    while (true) {\n\n        // case 1: previous exists\n        if (data.previous) {\n            if (data.previous->m_monitor != restrict) {\n                data = dataFor(data.previous.lock());\n                continue;\n            }\n\n            break;\n        }\n\n        // case 2: previous doesnt exist, but we have mon\n        if (data.previousMon) {\n            if (data.previousMon != restrict)\n                return nullptr;\n\n            break;\n        }\n\n        // case 3: no mon and no previous\n        return nullptr;\n    }\n\n    return &data;\n}\n\nSWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict) {\n    const auto DATA = previousWorkspace(ws, restrict);\n    if (!DATA)\n        return SWorkspaceIDName{.id = WORKSPACE_INVALID};\n\n    return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0};\n}\n\nvoid CWorkspaceHistoryTracker::setLastWorkspaceData(PHLWORKSPACE w) {\n    if (!w) {\n        m_lastWorkspaceData = {};\n        return;\n    }\n\n    m_lastWorkspaceData.workspace     = w;\n    m_lastWorkspaceData.workspaceID   = w->m_id;\n    m_lastWorkspaceData.workspaceName = w->m_name;\n    m_lastWorkspaceData.monitor       = w->m_monitor;\n}\n"
  },
  {
    "path": "src/desktop/history/WorkspaceHistoryTracker.hpp",
    "content": "#pragma once\n\n#include \"../DesktopTypes.hpp\"\n#include \"../../SharedDefs.hpp\"\n#include \"../../macros.hpp\"\n#include \"../../helpers/MiscFunctions.hpp\"\n\n#include <vector>\n\nnamespace Desktop::History {\n    class CWorkspaceHistoryTracker {\n      public:\n        CWorkspaceHistoryTracker();\n        ~CWorkspaceHistoryTracker() = default;\n\n        CWorkspaceHistoryTracker(const CWorkspaceHistoryTracker&) = delete;\n        CWorkspaceHistoryTracker(CWorkspaceHistoryTracker&)       = delete;\n        CWorkspaceHistoryTracker(CWorkspaceHistoryTracker&&)      = delete;\n\n        struct SWorkspacePreviousData {\n            PHLWORKSPACEREF workspace;\n            PHLWORKSPACEREF previous;\n            PHLMONITORREF   previousMon;\n            std::string     previousName = \"\";\n            WORKSPACEID     previousID   = WORKSPACE_INVALID;\n        };\n\n        const SWorkspacePreviousData* previousWorkspace(PHLWORKSPACE ws);\n        SWorkspaceIDName              previousWorkspaceIDName(PHLWORKSPACE ws);\n\n        const SWorkspacePreviousData* previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict);\n        SWorkspaceIDName              previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict);\n\n      private:\n        struct SLastWorkspaceData {\n            PHLMONITORREF   monitor;\n            PHLWORKSPACEREF workspace;\n            std::string     workspaceName = \"\";\n            WORKSPACEID     workspaceID   = WORKSPACE_INVALID;\n        } m_lastWorkspaceData;\n\n        std::vector<SWorkspacePreviousData> m_datas;\n\n        void                                track(PHLWORKSPACE w);\n        void                                gc();\n        void                                setLastWorkspaceData(PHLWORKSPACE w);\n\n        SWorkspacePreviousData&             dataFor(PHLWORKSPACE ws);\n    };\n\n    SP<CWorkspaceHistoryTracker> workspaceTracker();\n};"
  },
  {
    "path": "src/desktop/reserved/ReservedArea.cpp",
    "content": "#include \"ReservedArea.hpp\"\n#include \"../../macros.hpp\"\n\nusing namespace Desktop;\n\n// fuck me. Writing this at 11pm, and I have an in-class test tomorrow.\n// I am failing that bitch\n\nCReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl.clamp({0, 0})), m_initialBottomRight(br.clamp({0, 0})) {\n    calculate();\n}\n\nCReservedArea::CReservedArea(double top, double right, double bottom, double left) :\n    m_initialTopLeft(std::max(left, 0.0), std::max(top, 0.0)), m_initialBottomRight(std::max(right, 0.0), std::max(bottom, 0.0)) {\n    calculate();\n}\n\nCReservedArea::CReservedArea(const CBox& parent, const CBox& child) {\n    if (parent.empty() || child.empty())\n        return; // empty reserved area\n\n    ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001}));\n    ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001}));\n\n    m_initialTopLeft     = child.pos() - parent.pos();\n    m_initialBottomRight = (parent.pos() + parent.size()) - (child.pos() + child.size());\n\n    calculate();\n}\n\nvoid CReservedArea::calculate() {\n    m_bottomRight = m_initialBottomRight;\n    m_topLeft     = m_initialTopLeft;\n\n    for (const auto& e : m_dynamicReserved) {\n        m_bottomRight += e.bottomRight;\n        m_topLeft += e.topLeft;\n    }\n}\n\nCBox CReservedArea::apply(const CBox& other) const {\n    auto c = other.copy();\n    c.x += m_topLeft.x;\n    c.y += m_topLeft.y;\n    c.w -= m_topLeft.x + m_bottomRight.x;\n    c.h -= m_topLeft.y + m_bottomRight.y;\n    return c;\n}\n\nvoid CReservedArea::applyip(CBox& other) const {\n    other.x += m_topLeft.x;\n    other.y += m_topLeft.y;\n    other.w -= m_topLeft.x + m_bottomRight.x;\n    other.h -= m_topLeft.y + m_bottomRight.y;\n}\n\nbool CReservedArea::operator==(const CReservedArea& other) const {\n    return other.m_bottomRight == m_bottomRight && other.m_topLeft == m_topLeft;\n}\n\ndouble CReservedArea::left() const {\n    return m_topLeft.x;\n}\n\ndouble CReservedArea::right() const {\n    return m_bottomRight.x;\n}\n\ndouble CReservedArea::top() const {\n    return m_topLeft.y;\n}\n\ndouble CReservedArea::bottom() const {\n    return m_bottomRight.y;\n}\n\nvoid CReservedArea::resetType(eReservedDynamicType t) {\n    m_dynamicReserved[t] = {};\n    calculate();\n}\n\nvoid CReservedArea::addType(eReservedDynamicType t, const Vector2D& topLeft, const Vector2D& bottomRight) {\n    auto& ref = m_dynamicReserved[t];\n    ref.topLeft += topLeft;\n    ref.bottomRight += bottomRight;\n    ref.topLeft     = ref.topLeft.clamp({0, 0});\n    ref.bottomRight = ref.bottomRight.clamp({0, 0});\n    calculate();\n}\n\nvoid CReservedArea::addType(eReservedDynamicType t, const CReservedArea& area) {\n    addType(t, {area.left(), area.top()}, {area.right(), area.bottom()});\n}\n"
  },
  {
    "path": "src/desktop/reserved/ReservedArea.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include <array>\n\nnamespace Desktop {\n    enum eReservedDynamicType : uint8_t {\n        RESERVED_DYNAMIC_TYPE_LS = 0,\n        RESERVED_DYNAMIC_TYPE_ERROR_BAR,\n\n        RESERVED_DYNAMIC_TYPE_END,\n    };\n\n    class CReservedArea {\n      public:\n        CReservedArea() = default;\n        CReservedArea(const Vector2D& tl, const Vector2D& br);\n        CReservedArea(double top, double right, double bottom, double left);\n        CReservedArea(const CBox& parent, const CBox& child);\n        ~CReservedArea() = default;\n\n        CBox   apply(const CBox& other) const;\n        void   applyip(CBox& other) const;\n\n        void   resetType(eReservedDynamicType);\n        void   addType(eReservedDynamicType, const Vector2D& topLeft, const Vector2D& bottomRight);\n        void   addType(eReservedDynamicType, const CReservedArea& area);\n\n        double left() const;\n        double right() const;\n        double top() const;\n        double bottom() const;\n\n        bool   operator==(const CReservedArea& other) const;\n\n      private:\n        void     calculate();\n\n        Vector2D m_topLeft, m_bottomRight;\n        Vector2D m_initialTopLeft, m_initialBottomRight;\n\n        struct SDynamicData {\n            Vector2D topLeft, bottomRight;\n        };\n\n        std::array<SDynamicData, RESERVED_DYNAMIC_TYPE_END> m_dynamicReserved;\n    };\n};"
  },
  {
    "path": "src/desktop/rule/Engine.cpp",
    "content": "#include \"Engine.hpp\"\n#include \"Rule.hpp\"\n#include \"../view/LayerSurface.hpp\"\n#include \"../../Compositor.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\nSP<CRuleEngine> Rule::ruleEngine() {\n    static SP<CRuleEngine> engine = makeShared<CRuleEngine>();\n    return engine;\n}\n\nvoid CRuleEngine::registerRule(SP<IRule>&& rule) {\n    m_rules.emplace_back(std::move(rule));\n}\n\nvoid CRuleEngine::unregisterRule(const std::string& name) {\n    if (name.empty())\n        return;\n\n    std::erase_if(m_rules, [&name](const auto& el) { return el->name() == name; });\n}\n\nvoid CRuleEngine::unregisterRule(const SP<IRule>& rule) {\n    std::erase(m_rules, rule);\n    cleanExecRules();\n}\n\nvoid CRuleEngine::cleanExecRules() {\n    std::erase_if(m_rules, [](const auto& e) { return e->isExecRule() && e->execExpired(); });\n}\n\nvoid CRuleEngine::updateAllRules() {\n    cleanExecRules();\n    for (const auto& w : g_pCompositor->m_windows) {\n        if (!validMapped(w) || w->isHidden())\n            continue;\n\n        w->m_ruleApplicator->propertiesChanged(RULE_PROP_ALL);\n    }\n    for (const auto& ls : g_pCompositor->m_layers) {\n        if (!validMapped(ls))\n            continue;\n\n        ls->m_ruleApplicator->propertiesChanged(RULE_PROP_ALL);\n    }\n}\n\nvoid CRuleEngine::clearAllRules() {\n    std::erase_if(m_rules, [](const auto& e) { return !e->isExecRule() || e->execExpired(); });\n}\n\nconst std::vector<SP<IRule>>& CRuleEngine::rules() {\n    return m_rules;\n}\n"
  },
  {
    "path": "src/desktop/rule/Engine.hpp",
    "content": "#pragma once\n\n#include \"Rule.hpp\"\n\nnamespace Desktop::Rule {\n    class CRuleEngine {\n      public:\n        CRuleEngine()  = default;\n        ~CRuleEngine() = default;\n\n        void                          registerRule(SP<IRule>&& rule);\n        void                          unregisterRule(const std::string& name);\n        void                          unregisterRule(const SP<IRule>& rule);\n        void                          updateAllRules();\n        void                          cleanExecRules();\n        void                          clearAllRules();\n        const std::vector<SP<IRule>>& rules();\n\n      private:\n        std::vector<SP<IRule>> m_rules;\n    };\n\n    SP<CRuleEngine> ruleEngine();\n}"
  },
  {
    "path": "src/desktop/rule/Rule.cpp",
    "content": "#include \"Rule.hpp\"\n#include \"../../debug/log/Logger.hpp\"\n#include <re2/re2.h>\n\n#include \"matchEngine/RegexMatchEngine.hpp\"\n#include \"matchEngine/BoolMatchEngine.hpp\"\n#include \"matchEngine/IntMatchEngine.hpp\"\n#include \"matchEngine/WorkspaceMatchEngine.hpp\"\n#include \"matchEngine/TagMatchEngine.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\nstatic const std::unordered_map<eRuleProperty, std::string> MATCH_PROP_STRINGS = {\n    {RULE_PROP_CLASS, \"class\"},                                        //\n    {RULE_PROP_TITLE, \"title\"},                                        //\n    {RULE_PROP_INITIAL_CLASS, \"initial_class\"},                        //\n    {RULE_PROP_INITIAL_TITLE, \"initial_title\"},                        //\n    {RULE_PROP_FLOATING, \"float\"},                                     //\n    {RULE_PROP_TAG, \"tag\"},                                            //\n    {RULE_PROP_XWAYLAND, \"xwayland\"},                                  //\n    {RULE_PROP_FULLSCREEN, \"fullscreen\"},                              //\n    {RULE_PROP_PINNED, \"pin\"},                                         //\n    {RULE_PROP_FOCUS, \"focus\"},                                        //\n    {RULE_PROP_GROUP, \"group\"},                                        //\n    {RULE_PROP_MODAL, \"modal\"},                                        //\n    {RULE_PROP_FULLSCREENSTATE_INTERNAL, \"fullscreen_state_internal\"}, //\n    {RULE_PROP_FULLSCREENSTATE_CLIENT, \"fullscreen_state_client\"},     //\n    {RULE_PROP_ON_WORKSPACE, \"workspace\"},                             //\n    {RULE_PROP_CONTENT, \"content\"},                                    //\n    {RULE_PROP_XDG_TAG, \"xdg_tag\"},                                    //\n    {RULE_PROP_NAMESPACE, \"namespace\"},                                //\n};\n\nstatic const std::unordered_map<eRuleProperty, eRuleMatchEngine> RULE_ENGINES = {\n    {RULE_PROP_CLASS, RULE_MATCH_ENGINE_REGEX},                  //\n    {RULE_PROP_TITLE, RULE_MATCH_ENGINE_REGEX},                  //\n    {RULE_PROP_INITIAL_CLASS, RULE_MATCH_ENGINE_REGEX},          //\n    {RULE_PROP_INITIAL_TITLE, RULE_MATCH_ENGINE_REGEX},          //\n    {RULE_PROP_FLOATING, RULE_MATCH_ENGINE_BOOL},                //\n    {RULE_PROP_TAG, RULE_MATCH_ENGINE_TAG},                      //\n    {RULE_PROP_XWAYLAND, RULE_MATCH_ENGINE_BOOL},                //\n    {RULE_PROP_FULLSCREEN, RULE_MATCH_ENGINE_BOOL},              //\n    {RULE_PROP_PINNED, RULE_MATCH_ENGINE_BOOL},                  //\n    {RULE_PROP_FOCUS, RULE_MATCH_ENGINE_BOOL},                   //\n    {RULE_PROP_GROUP, RULE_MATCH_ENGINE_BOOL},                   //\n    {RULE_PROP_MODAL, RULE_MATCH_ENGINE_BOOL},                   //\n    {RULE_PROP_FULLSCREENSTATE_INTERNAL, RULE_MATCH_ENGINE_INT}, //\n    {RULE_PROP_FULLSCREENSTATE_CLIENT, RULE_MATCH_ENGINE_INT},   //\n    {RULE_PROP_ON_WORKSPACE, RULE_MATCH_ENGINE_WORKSPACE},       //\n    {RULE_PROP_CONTENT, RULE_MATCH_ENGINE_REGEX},                //\n    {RULE_PROP_XDG_TAG, RULE_MATCH_ENGINE_REGEX},                //\n    {RULE_PROP_NAMESPACE, RULE_MATCH_ENGINE_REGEX},              //\n    {RULE_PROP_EXEC_TOKEN, RULE_MATCH_ENGINE_REGEX},             //\n    {RULE_PROP_EXEC_PID, RULE_MATCH_ENGINE_INT},                 //\n};\n\nconst std::vector<std::string>& Rule::allMatchPropStrings() {\n    static std::vector<std::string> strings;\n    static bool                     once = true;\n    if (once) {\n        for (const auto& [k, v] : MATCH_PROP_STRINGS) {\n            strings.emplace_back(v);\n        }\n        once = false;\n    }\n    return strings;\n}\n\nstd::optional<eRuleProperty> Rule::matchPropFromString(const std::string_view& s) {\n    const auto IT = std::ranges::find_if(MATCH_PROP_STRINGS, [&s](const auto& el) { return el.second == s; });\n    if (IT == MATCH_PROP_STRINGS.end())\n        return std::nullopt;\n\n    return IT->first;\n}\n\nstd::optional<eRuleProperty> Rule::matchPropFromString(const std::string& s) {\n    return matchPropFromString(std::string_view{s});\n}\n\nIRule::IRule(const std::string& name) : m_name(name) {\n    ;\n}\n\nvoid IRule::registerMatch(eRuleProperty p, const std::string& s) {\n    if (!RULE_ENGINES.contains(p)) {\n        Log::logger->log(Log::ERR, \"BUG THIS: IRule: RULE_ENGINES does not contain rule idx {}\", sc<std::underlying_type_t<eRuleProperty>>(p));\n        return;\n    }\n\n    switch (RULE_ENGINES.at(p)) {\n        case RULE_MATCH_ENGINE_REGEX: m_matchEngines[p] = makeUnique<CRegexMatchEngine>(s); break;\n        case RULE_MATCH_ENGINE_BOOL: m_matchEngines[p] = makeUnique<CBoolMatchEngine>(s); break;\n        case RULE_MATCH_ENGINE_INT: m_matchEngines[p] = makeUnique<CIntMatchEngine>(s); break;\n        case RULE_MATCH_ENGINE_WORKSPACE: m_matchEngines[p] = makeUnique<CWorkspaceMatchEngine>(s); break;\n        case RULE_MATCH_ENGINE_TAG: m_matchEngines[p] = makeUnique<CTagMatchEngine>(s); break;\n    }\n\n    m_mask |= p;\n}\n\nstd::underlying_type_t<eRuleProperty> IRule::getPropertiesMask() {\n    return m_mask;\n}\n\nbool IRule::has(eRuleProperty p) {\n    return m_matchEngines.contains(p);\n}\n\nbool IRule::matches(eRuleProperty p, const std::string& s) {\n    if (!has(p))\n        return false;\n\n    return m_matchEngines[p]->match(s);\n}\n\nbool IRule::matches(eRuleProperty p, bool b) {\n    if (!has(p))\n        return false;\n\n    return m_matchEngines[p]->match(b);\n}\n\nconst std::string& IRule::name() {\n    return m_name;\n}\n\nvoid IRule::markAsExecRule(const std::string& token, uint64_t pid, bool persistent) {\n    m_execData.isExecRule       = true;\n    m_execData.isExecPersistent = persistent;\n    m_execData.token            = token;\n    m_execData.pid              = pid;\n    m_execData.expiresAt        = Time::steadyNow() + std::chrono::minutes(1);\n}\n\nbool IRule::isExecRule() {\n    return m_execData.isExecRule;\n}\n\nbool IRule::isExecPersistent() {\n    return m_execData.isExecPersistent;\n}\n\nbool IRule::execExpired() {\n    return Time::steadyNow() > m_execData.expiresAt;\n}\n\nconst std::string& IRule::execToken() {\n    return m_execData.token;\n}\n"
  },
  {
    "path": "src/desktop/rule/Rule.hpp",
    "content": "#pragma once\n\n#include \"matchEngine/MatchEngine.hpp\"\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n#include <vector>\n#include <unordered_map>\n#include <optional>\n\nnamespace Desktop::Rule {\n    enum eRuleProperty : uint32_t {\n        RULE_PROP_NONE                     = 0,\n        RULE_PROP_CLASS                    = (1 << 0),\n        RULE_PROP_TITLE                    = (1 << 1),\n        RULE_PROP_INITIAL_CLASS            = (1 << 2),\n        RULE_PROP_INITIAL_TITLE            = (1 << 3),\n        RULE_PROP_FLOATING                 = (1 << 4),\n        RULE_PROP_TAG                      = (1 << 5),\n        RULE_PROP_XWAYLAND                 = (1 << 6),\n        RULE_PROP_FULLSCREEN               = (1 << 7),\n        RULE_PROP_PINNED                   = (1 << 8),\n        RULE_PROP_FOCUS                    = (1 << 9),\n        RULE_PROP_GROUP                    = (1 << 10),\n        RULE_PROP_MODAL                    = (1 << 11),\n        RULE_PROP_FULLSCREENSTATE_INTERNAL = (1 << 12),\n        RULE_PROP_FULLSCREENSTATE_CLIENT   = (1 << 13),\n        RULE_PROP_ON_WORKSPACE             = (1 << 14),\n        RULE_PROP_CONTENT                  = (1 << 15),\n        RULE_PROP_XDG_TAG                  = (1 << 16),\n        RULE_PROP_NAMESPACE                = (1 << 17),\n        RULE_PROP_EXEC_TOKEN               = (1 << 18),\n        RULE_PROP_EXEC_PID                 = (1 << 19),\n\n        RULE_PROP_ALL = std::numeric_limits<std::underlying_type_t<eRuleProperty>>::max(),\n    };\n\n    enum eRuleType : uint8_t {\n        RULE_TYPE_WINDOW = 0,\n        RULE_TYPE_LAYER,\n    };\n\n    std::optional<eRuleProperty>    matchPropFromString(const std::string& s);\n    std::optional<eRuleProperty>    matchPropFromString(const std::string_view& s);\n    const std::vector<std::string>& allMatchPropStrings();\n\n    class IRule {\n      public:\n        virtual ~IRule() = default;\n\n        virtual eRuleType                             type() = 0;\n        virtual std::underlying_type_t<eRuleProperty> getPropertiesMask();\n\n        void                                          registerMatch(eRuleProperty, const std::string&);\n        void                                          markAsExecRule(const std::string& token, uint64_t pid, bool persistent = false);\n        bool                                          isExecRule();\n        bool                                          isExecPersistent();\n        bool                                          execExpired();\n        const std::string&                            execToken();\n\n        const std::string&                            name();\n\n      protected:\n        IRule(const std::string& name = \"\");\n\n        bool matches(eRuleProperty, const std::string& s);\n        bool matches(eRuleProperty, bool b);\n        bool has(eRuleProperty);\n\n        //\n        std::unordered_map<eRuleProperty, UP<IMatchEngine>> m_matchEngines;\n\n      private:\n        std::underlying_type_t<eRuleProperty> m_mask = 0;\n        std::string                           m_name = \"\";\n\n        struct {\n            bool            isExecRule       = false;\n            bool            isExecPersistent = false;\n            std::string     token;\n            uint64_t        pid = 0;\n            Time::steady_tp expiresAt;\n        } m_execData;\n    };\n}\n"
  },
  {
    "path": "src/desktop/rule/effect/EffectContainer.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n#include <type_traits>\n#include <cstdint>\n#include <optional>\n#include <algorithm>\n\nnamespace Desktop::Rule {\n    template <typename T>\n    class IEffectContainer {\n        static_assert(std::is_enum_v<T>);\n\n      protected:\n        const std::string DEFAULT_MISSING_KEY = \"\";\n\n      public:\n        // Make sure we're using at least a uint16_t for dynamic registrations to not overflow.\n        // 32k should be enough\n        using storageType = std::conditional_t<(sizeof(std::underlying_type_t<T>) >= 2), std::underlying_type_t<T>, uint16_t>;\n\n        IEffectContainer(std::vector<std::string>&& defaultKeys) : m_keys(std::move(defaultKeys)), m_originalSize(m_keys.size()) {\n            ;\n        }\n        virtual ~IEffectContainer() = default;\n\n        virtual storageType registerEffect(std::string&& name) {\n            if (m_keys.size() >= std::numeric_limits<storageType>::max())\n                return 0;\n            if (auto it = std::ranges::find(m_keys, name); it != m_keys.end())\n                return it - m_keys.begin();\n            m_keys.emplace_back(std::move(name));\n            return m_keys.size() - 1;\n        }\n\n        virtual void unregisterEffect(storageType id) {\n            if (id >= m_keys.size())\n                return;\n\n            m_keys[id] = DEFAULT_MISSING_KEY;\n        }\n\n        virtual void unregisterEffect(const std::string& name) {\n            for (auto& key : m_keys) {\n                if (key == name) {\n                    key = DEFAULT_MISSING_KEY;\n                    break;\n                }\n            }\n        }\n\n        virtual const std::string& get(storageType idx) {\n            if (idx >= m_keys.size())\n                return DEFAULT_MISSING_KEY;\n\n            return m_keys[idx];\n        }\n\n        virtual std::optional<storageType> get(const std::string_view& s) {\n            for (storageType i = 0; i < m_keys.size(); ++i) {\n                if (m_keys[i] == s)\n                    return i;\n            }\n\n            return std::nullopt;\n        }\n\n        virtual const std::vector<std::string>& allEffectStrings() {\n            return m_keys;\n        }\n\n        // whether the effect has been added dynamically as opposed to in the ctor.\n        virtual bool isEffectDynamic(storageType i) {\n            return i >= m_originalSize;\n        }\n\n      protected:\n        std::vector<std::string> m_keys;\n        size_t                   m_originalSize = 0;\n    };\n};\n"
  },
  {
    "path": "src/desktop/rule/layerRule/LayerRule.cpp",
    "content": "#include \"LayerRule.hpp\"\n#include \"../../../debug/log/Logger.hpp\"\n#include \"../../view/LayerSurface.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\nCLayerRule::CLayerRule(const std::string& name) : IRule(name) {\n    ;\n}\n\neRuleType CLayerRule::type() {\n    return RULE_TYPE_LAYER;\n}\n\nvoid CLayerRule::addEffect(CLayerRule::storageType e, const std::string& result) {\n    m_effects.emplace_back(std::make_pair<>(e, result));\n}\n\nconst std::vector<std::pair<CLayerRule::storageType, std::string>>& CLayerRule::effects() {\n    return m_effects;\n}\n\nbool CLayerRule::matches(PHLLS ls) {\n    if (m_matchEngines.empty())\n        return false;\n\n    for (const auto& [prop, engine] : m_matchEngines) {\n        switch (prop) {\n            default: {\n                Log::logger->log(Log::TRACE, \"CLayerRule::matches: skipping prop entry {}\", sc<std::underlying_type_t<eRuleProperty>>(prop));\n                break;\n            }\n\n            case RULE_PROP_NAMESPACE:\n                if (!engine->match(ls->m_namespace))\n                    return false;\n                break;\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/desktop/rule/layerRule/LayerRule.hpp",
    "content": "#pragma once\n\n#include \"../Rule.hpp\"\n#include \"../../DesktopTypes.hpp\"\n#include \"LayerRuleEffectContainer.hpp\"\n\nnamespace Desktop::Rule {\n    class CLayerRule : public IRule {\n      public:\n        using storageType = CLayerRuleEffectContainer::storageType;\n\n        CLayerRule(const std::string& name = \"\");\n        virtual ~CLayerRule() = default;\n\n        virtual eRuleType                                       type();\n\n        void                                                    addEffect(storageType e, const std::string& result);\n        const std::vector<std::pair<storageType, std::string>>& effects();\n\n        bool                                                    matches(PHLLS w);\n\n      private:\n        std::vector<std::pair<storageType, std::string>> m_effects;\n    };\n};\n"
  },
  {
    "path": "src/desktop/rule/layerRule/LayerRuleApplicator.cpp",
    "content": "#include \"LayerRuleApplicator.hpp\"\n#include \"LayerRule.hpp\"\n#include \"../Engine.hpp\"\n#include \"../../view/LayerSurface.hpp\"\n#include \"../../types/OverridableVar.hpp\"\n#include \"../../../helpers/MiscFunctions.hpp\"\n#include \"../../../event/EventBus.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\nCLayerRuleApplicator::CLayerRuleApplicator(PHLLS ls) : m_ls(ls) {\n    ;\n}\n\nvoid CLayerRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio) {\n    // TODO: fucking kill me, is there a better way to do this?\n\n#define UNSET(x)                                                                                                                                                                   \\\n    if (m_##x.second & props) {                                                                                                                                                    \\\n        if (prio == Types::PRIORITY_WINDOW_RULE)                                                                                                                                   \\\n            m_##x.second &= ~props;                                                                                                                                                \\\n        m_##x.first.unset(prio);                                                                                                                                                   \\\n    }\n\n    UNSET(noanim)\n    UNSET(blur)\n    UNSET(blurPopups)\n    UNSET(dimAround)\n    UNSET(xray)\n    UNSET(noScreenShare)\n    UNSET(order)\n    UNSET(aboveLock)\n    UNSET(ignoreAlpha)\n    UNSET(animationStyle)\n\n#undef UNSET\n\n    if (prio == Types::PRIORITY_WINDOW_RULE)\n        std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; });\n}\n\nvoid CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {\n    for (const auto& [key, effect] : rule->effects()) {\n        switch (key) {\n            default: {\n                if (key <= LAYER_RULE_EFFECT_LAST_STATIC) {\n                    Log::logger->log(Log::TRACE, \"CLayerRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic\", sc<std::underlying_type_t<eLayerRuleEffect>>(key));\n                    break;\n                }\n\n                // custom type, add to our vec\n                if (!m_otherProps.props.contains(key)) {\n                    m_otherProps.props.emplace(key,\n                                               makeUnique<SCustomPropContainer>(SCustomPropContainer{\n                                                   .idx      = key,\n                                                   .propMask = rule->getPropertiesMask(),\n                                                   .effect   = effect,\n                                               }));\n                } else {\n                    auto& e = m_otherProps.props[key];\n                    e->propMask |= rule->getPropertiesMask();\n                    e->effect = effect;\n                }\n\n                break;\n            }\n            case LAYER_RULE_EFFECT_NONE: {\n                Log::logger->log(Log::ERR, \"CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??\");\n                break;\n            }\n            case LAYER_RULE_EFFECT_NO_ANIM: {\n                m_noanim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noanim.second |= rule->getPropertiesMask();\n                break;\n            }\n            case LAYER_RULE_EFFECT_BLUR: {\n                m_blur.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_blur.second |= rule->getPropertiesMask();\n                break;\n            }\n            case LAYER_RULE_EFFECT_BLUR_POPUPS: {\n                m_blurPopups.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_blurPopups.second |= rule->getPropertiesMask();\n                break;\n            }\n            case LAYER_RULE_EFFECT_DIM_AROUND: {\n                m_dimAround.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_dimAround.second |= rule->getPropertiesMask();\n                break;\n            }\n            case LAYER_RULE_EFFECT_XRAY: {\n                m_xray.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_xray.second |= rule->getPropertiesMask();\n                break;\n            }\n            case LAYER_RULE_EFFECT_NO_SCREEN_SHARE: {\n                m_noScreenShare.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noScreenShare.second |= rule->getPropertiesMask();\n                break;\n            }\n            case LAYER_RULE_EFFECT_ORDER: {\n                try {\n                    m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);\n                    m_order.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CLayerRuleApplicator::applyDynamicRule: invalid order {}\", effect); }\n                break;\n            }\n            case LAYER_RULE_EFFECT_ABOVE_LOCK: {\n                try {\n                    m_aboveLock.first.set(std::clamp(std::stoull(effect), 0ULL, 2ULL), Types::PRIORITY_WINDOW_RULE);\n                    m_aboveLock.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CLayerRuleApplicator::applyDynamicRule: invalid order {}\", effect); }\n                break;\n            }\n            case LAYER_RULE_EFFECT_IGNORE_ALPHA: {\n                try {\n                    m_ignoreAlpha.first.set(std::clamp(std::stof(effect), 0.F, 1.F), Types::PRIORITY_WINDOW_RULE);\n                    m_ignoreAlpha.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CLayerRuleApplicator::applyDynamicRule: invalid order {}\", effect); }\n                break;\n            }\n            case LAYER_RULE_EFFECT_ANIMATION: {\n                m_animationStyle.first.set(effect, Types::PRIORITY_WINDOW_RULE);\n                m_animationStyle.second |= rule->getPropertiesMask();\n                break;\n            }\n        }\n    }\n}\n\nvoid CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProperty> props) {\n    if (!m_ls)\n        return;\n\n    resetProps(props);\n\n    // FIXME: this will not update properties correctly if we implement dynamic rules for\n    // layers, due to effects overlapping on 0 prop intersection.\n    // See WindowRule.cpp, and ::propertiesChanged there.\n\n    for (const auto& r : ruleEngine()->rules()) {\n        if (r->type() != RULE_TYPE_LAYER)\n            continue;\n\n        if (!(r->getPropertiesMask() & props))\n            continue;\n\n        auto wr = reinterpretPointerCast<CLayerRule>(r);\n\n        if (!wr->matches(m_ls.lock()))\n            continue;\n\n        applyDynamicRule(wr);\n    }\n\n    // for plugins\n    Event::bus()->m_events.layer.updateRules.emit(m_ls.lock());\n}\n"
  },
  {
    "path": "src/desktop/rule/layerRule/LayerRuleApplicator.hpp",
    "content": "#pragma once\n\n#include \"LayerRuleEffectContainer.hpp\"\n#include \"../../DesktopTypes.hpp\"\n#include \"../Rule.hpp\"\n#include \"../../types/OverridableVar.hpp\"\n#include \"../../../helpers/math/Math.hpp\"\n#include \"../../../config/ConfigDataValues.hpp\"\n\nnamespace Desktop::Rule {\n    class CLayerRule;\n\n    class CLayerRuleApplicator {\n      public:\n        CLayerRuleApplicator(PHLLS ls);\n        ~CLayerRuleApplicator() = default;\n\n        CLayerRuleApplicator(const CLayerRuleApplicator&) = delete;\n        CLayerRuleApplicator(CLayerRuleApplicator&)       = delete;\n        CLayerRuleApplicator(CLayerRuleApplicator&&)      = delete;\n\n        void propertiesChanged(std::underlying_type_t<eRuleProperty> props);\n        void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);\n\n        struct SCustomPropContainer {\n            CLayerRuleEffectContainer::storageType idx      = LAYER_RULE_EFFECT_NONE;\n            std::underlying_type_t<eRuleProperty>  propMask = RULE_PROP_NONE;\n            std::string                            effect;\n        };\n\n        // This struct holds props that were dynamically registered. Plugins may read this.\n        struct {\n            std::unordered_map<CLayerRuleEffectContainer::storageType, UP<SCustomPropContainer>> props;\n        } m_otherProps;\n\n#define COMMA ,\n#define DEFINE_PROP(type, name, def)                                                                                                                                               \\\n  private:                                                                                                                                                                         \\\n    std::pair<Types::COverridableVar<type>, std::underlying_type_t<eRuleProperty>> m_##name = {def, RULE_PROP_NONE};                                                               \\\n                                                                                                                                                                                   \\\n  public:                                                                                                                                                                          \\\n    Types::COverridableVar<type>& name() {                                                                                                                                         \\\n        return m_##name.first;                                                                                                                                                     \\\n    }                                                                                                                                                                              \\\n    void name##Override(const Types::COverridableVar<type>& other) {                                                                                                               \\\n        m_##name.first = other;                                                                                                                                                    \\\n    }\n\n        // dynamic props\n        DEFINE_PROP(bool, noanim, false)\n        DEFINE_PROP(bool, blur, false)\n        DEFINE_PROP(bool, blurPopups, false)\n        DEFINE_PROP(bool, dimAround, false)\n        DEFINE_PROP(bool, xray, false)\n        DEFINE_PROP(bool, noScreenShare, false)\n\n        DEFINE_PROP(Hyprlang::INT, order, 0)\n        DEFINE_PROP(Hyprlang::INT, aboveLock, 0)\n\n        DEFINE_PROP(Hyprlang::FLOAT, ignoreAlpha, 0.F)\n\n        DEFINE_PROP(std::string, animationStyle, std::string(\"\"))\n\n#undef COMMA\n#undef DEFINE_PROP\n\n      private:\n        PHLLSREF m_ls;\n\n        void     applyDynamicRule(const SP<CLayerRule>& rule);\n    };\n};\n"
  },
  {
    "path": "src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp",
    "content": "#include \"LayerRuleEffectContainer.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\n//\nSP<CLayerRuleEffectContainer> Rule::layerEffects() {\n    static SP<CLayerRuleEffectContainer> container = makeShared<CLayerRuleEffectContainer>();\n    return container;\n}\n\nstatic const std::vector<std::string> EFFECT_STRINGS = {\n    \"__internal_none\",        //\n    \"no_anim\",                //\n    \"blur\",                   //\n    \"blur_popups\",            //\n    \"ignore_alpha\",           //\n    \"dim_around\",             //\n    \"xray\",                   //\n    \"animation\",              //\n    \"order\",                  //\n    \"above_lock\",             //\n    \"no_screen_share\",        //\n    \"__internal_last_static\", //\n};\n\n// This is here so that if we change the rules, we get reminded to update\n// the strings.\nstatic_assert(LAYER_RULE_EFFECT_LAST_STATIC == 11);\n\nCLayerRuleEffectContainer::CLayerRuleEffectContainer() : IEffectContainer<eLayerRuleEffect>(std::vector<std::string>{EFFECT_STRINGS}) {\n    ;\n}\n"
  },
  {
    "path": "src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp",
    "content": "#pragma once\n\n#include \"../effect/EffectContainer.hpp\"\n#include \"../../../helpers/memory/Memory.hpp\"\n\n#pragma once\n\nnamespace Desktop::Rule {\n    enum eLayerRuleEffect : uint8_t {\n        LAYER_RULE_EFFECT_NONE = 0,\n\n        LAYER_RULE_EFFECT_NO_ANIM,\n        LAYER_RULE_EFFECT_BLUR,\n        LAYER_RULE_EFFECT_BLUR_POPUPS,\n        LAYER_RULE_EFFECT_IGNORE_ALPHA,\n        LAYER_RULE_EFFECT_DIM_AROUND,\n        LAYER_RULE_EFFECT_XRAY,\n        LAYER_RULE_EFFECT_ANIMATION,\n        LAYER_RULE_EFFECT_ORDER,\n        LAYER_RULE_EFFECT_ABOVE_LOCK,\n        LAYER_RULE_EFFECT_NO_SCREEN_SHARE,\n\n        LAYER_RULE_EFFECT_LAST_STATIC,\n    };\n\n    class CLayerRuleEffectContainer : public IEffectContainer<eLayerRuleEffect> {\n      public:\n        CLayerRuleEffectContainer();\n        virtual ~CLayerRuleEffectContainer() = default;\n    };\n\n    SP<CLayerRuleEffectContainer> layerEffects();\n};"
  },
  {
    "path": "src/desktop/rule/matchEngine/BoolMatchEngine.cpp",
    "content": "#include \"BoolMatchEngine.hpp\"\n#include \"../../../helpers/MiscFunctions.hpp\"\n\nusing namespace Desktop::Rule;\n\nCBoolMatchEngine::CBoolMatchEngine(const std::string& s) : m_value(truthy(s)) {\n    ;\n}\n\nbool CBoolMatchEngine::match(bool other) {\n    return other == m_value;\n}\n"
  },
  {
    "path": "src/desktop/rule/matchEngine/BoolMatchEngine.hpp",
    "content": "#pragma once\n\n#include \"MatchEngine.hpp\"\n\nnamespace Desktop::Rule {\n    class CBoolMatchEngine : public IMatchEngine {\n      public:\n        CBoolMatchEngine(const std::string&);\n        virtual ~CBoolMatchEngine() = default;\n\n        virtual bool match(bool other);\n\n      private:\n        bool m_value = false;\n    };\n}"
  },
  {
    "path": "src/desktop/rule/matchEngine/IntMatchEngine.cpp",
    "content": "#include \"IntMatchEngine.hpp\"\n#include \"../../../debug/log/Logger.hpp\"\n\nusing namespace Desktop::Rule;\n\nCIntMatchEngine::CIntMatchEngine(const std::string& s) {\n    try {\n        m_value = std::stoi(s);\n    } catch (...) { Log::logger->log(Log::ERR, \"CIntMatchEngine: invalid input {}\", s); }\n}\n\nbool CIntMatchEngine::match(int other) {\n    return m_value == other;\n}\n"
  },
  {
    "path": "src/desktop/rule/matchEngine/IntMatchEngine.hpp",
    "content": "#pragma once\n\n#include \"MatchEngine.hpp\"\n\nnamespace Desktop::Rule {\n    class CIntMatchEngine : public IMatchEngine {\n      public:\n        CIntMatchEngine(const std::string&);\n        virtual ~CIntMatchEngine() = default;\n\n        virtual bool match(int other);\n\n      private:\n        int m_value = 0;\n    };\n}"
  },
  {
    "path": "src/desktop/rule/matchEngine/MatchEngine.cpp",
    "content": "#include \"MatchEngine.hpp\"\n\nusing namespace Desktop::Rule;\n\nbool IMatchEngine::match(const std::string&) {\n    return false;\n}\n\nbool IMatchEngine::match(bool) {\n    return false;\n}\n\nbool IMatchEngine::match(int) {\n    return false;\n}\n\nbool IMatchEngine::match(PHLWORKSPACE) {\n    return false;\n}\n\nbool IMatchEngine::match(const CTagKeeper& keeper) {\n    return false;\n}\n"
  },
  {
    "path": "src/desktop/rule/matchEngine/MatchEngine.hpp",
    "content": "#pragma once\n\n#include \"../../DesktopTypes.hpp\"\n\nclass CTagKeeper;\n\nnamespace Desktop::Rule {\n    enum eRuleMatchEngine : uint8_t {\n        RULE_MATCH_ENGINE_REGEX = 0,\n        RULE_MATCH_ENGINE_BOOL,\n        RULE_MATCH_ENGINE_INT,\n        RULE_MATCH_ENGINE_WORKSPACE,\n        RULE_MATCH_ENGINE_TAG,\n    };\n\n    class IMatchEngine {\n      public:\n        virtual ~IMatchEngine() = default;\n        virtual bool match(const std::string&);\n        virtual bool match(bool);\n        virtual bool match(int);\n        virtual bool match(PHLWORKSPACE);\n        virtual bool match(const CTagKeeper& keeper);\n\n      protected:\n        IMatchEngine() = default;\n    };\n};"
  },
  {
    "path": "src/desktop/rule/matchEngine/RegexMatchEngine.cpp",
    "content": "#include \"RegexMatchEngine.hpp\"\n#include <re2/re2.h>\n\nusing namespace Desktop::Rule;\n\nCRegexMatchEngine::CRegexMatchEngine(const std::string& regex) {\n    if (regex.starts_with(\"negative:\")) {\n        m_negative = true;\n        m_regex    = makeUnique<re2::RE2>(regex.substr(9));\n        return;\n    }\n    m_regex = makeUnique<re2::RE2>(regex);\n}\n\nbool CRegexMatchEngine::match(const std::string& other) {\n    return re2::RE2::FullMatch(other, *m_regex) != m_negative;\n}"
  },
  {
    "path": "src/desktop/rule/matchEngine/RegexMatchEngine.hpp",
    "content": "#pragma once\n\n#include \"MatchEngine.hpp\"\n#include \"../../../helpers/memory/Memory.hpp\"\n\n//NOLINTNEXTLINE\nnamespace re2 {\n    class RE2;\n};\n\nnamespace Desktop::Rule {\n    class CRegexMatchEngine : public IMatchEngine {\n      public:\n        CRegexMatchEngine(const std::string& regex);\n        virtual ~CRegexMatchEngine() = default;\n\n        virtual bool match(const std::string& other);\n\n      private:\n        UP<re2::RE2> m_regex;\n        bool         m_negative = false;\n    };\n}"
  },
  {
    "path": "src/desktop/rule/matchEngine/TagMatchEngine.cpp",
    "content": "#include \"TagMatchEngine.hpp\"\n#include \"../../../helpers/TagKeeper.hpp\"\n#include <string>\n\nusing namespace Desktop::Rule;\n\nCTagMatchEngine::CTagMatchEngine(const std::string& tag) : m_tag(tag) {\n    ;\n}\n\nbool CTagMatchEngine::match(const CTagKeeper& keeper) {\n    return keeper.isTagged(m_tag);\n}\n"
  },
  {
    "path": "src/desktop/rule/matchEngine/TagMatchEngine.hpp",
    "content": "#pragma once\n\n#include \"MatchEngine.hpp\"\n#include <string>\n\nnamespace Desktop::Rule {\n    class CTagMatchEngine : public IMatchEngine {\n      public:\n        CTagMatchEngine(const std::string& tag);\n        virtual ~CTagMatchEngine() = default;\n\n        virtual bool match(const CTagKeeper& keeper);\n\n      private:\n        std::string m_tag;\n    };\n}\n"
  },
  {
    "path": "src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp",
    "content": "#include \"WorkspaceMatchEngine.hpp\"\n#include \"../../Workspace.hpp\"\n\nusing namespace Desktop::Rule;\n\nCWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s) {\n    ;\n}\n\nbool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) {\n    return ws && ws->matchesStaticSelector(m_value);\n}\n"
  },
  {
    "path": "src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp",
    "content": "#pragma once\n\n#include \"MatchEngine.hpp\"\n#include <string>\n\nnamespace Desktop::Rule {\n    class CWorkspaceMatchEngine : public IMatchEngine {\n      public:\n        CWorkspaceMatchEngine(const std::string&);\n        virtual ~CWorkspaceMatchEngine() = default;\n\n        virtual bool match(PHLWORKSPACE ws);\n\n      private:\n        std::string m_value = \"\";\n    };\n}\n"
  },
  {
    "path": "src/desktop/rule/utils/SetUtils.hpp",
    "content": "#pragma once\n\n#include <unordered_set>\n\nnamespace Desktop::Rule {\n    template <class T>\n    bool setsIntersect(const std::unordered_set<T>& A, const std::unordered_set<T>& B) {\n        if (A.size() > B.size())\n            return setsIntersect(B, A);\n\n        for (const auto& e : A) {\n            if (B.contains(e))\n                return true;\n        }\n        return false;\n    }\n};"
  },
  {
    "path": "src/desktop/rule/windowRule/WindowRule.cpp",
    "content": "#include \"WindowRule.hpp\"\n#include \"../../view/Window.hpp\"\n#include \"../../../helpers/Monitor.hpp\"\n#include \"../../../Compositor.hpp\"\n#include \"../../../managers/TokenManager.hpp\"\n#include \"../../../desktop/state/FocusState.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\nCWindowRule::CWindowRule(const std::string& name) : IRule(name) {\n    ;\n}\n\neRuleType CWindowRule::type() {\n    return RULE_TYPE_WINDOW;\n}\n\nvoid CWindowRule::addEffect(CWindowRule::storageType e, const std::string& result) {\n    m_effects.emplace_back(std::make_pair<>(e, result));\n    m_effectSet.emplace(e);\n}\n\nconst std::vector<std::pair<CWindowRule::storageType, std::string>>& CWindowRule::effects() {\n    return m_effects;\n}\n\nbool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {\n    if (m_matchEngines.empty())\n        return false;\n\n    for (const auto& [prop, engine] : m_matchEngines) {\n        switch (prop) {\n            default: {\n                Log::logger->log(Log::TRACE, \"CWindowRule::matches: skipping prop entry {}\", sc<std::underlying_type_t<eRuleProperty>>(prop));\n                break;\n            }\n\n            case RULE_PROP_TITLE:\n                if (!engine->match(w->m_title))\n                    return false;\n                break;\n            case RULE_PROP_INITIAL_TITLE:\n                if (!engine->match(w->m_initialTitle))\n                    return false;\n                break;\n            case RULE_PROP_CLASS:\n                if (!engine->match(w->m_class))\n                    return false;\n                break;\n            case RULE_PROP_INITIAL_CLASS:\n                if (!engine->match(w->m_initialClass))\n                    return false;\n                break;\n            case RULE_PROP_FLOATING:\n                if (!engine->match(w->m_isFloating))\n                    return false;\n                break;\n            case RULE_PROP_TAG:\n                if (!engine->match(w->m_ruleApplicator->m_tagKeeper))\n                    return false;\n                break;\n            case RULE_PROP_XWAYLAND:\n                if (!engine->match(w->m_isX11))\n                    return false;\n                break;\n            case RULE_PROP_FULLSCREEN:\n                if (!engine->match(w->m_fullscreenState.internal != 0))\n                    return false;\n                break;\n            case RULE_PROP_PINNED:\n                if (!engine->match(w->m_pinned))\n                    return false;\n                break;\n            case RULE_PROP_FOCUS:\n                if (!engine->match(Desktop::focusState()->window() == w))\n                    return false;\n                break;\n            case RULE_PROP_GROUP:\n                if (!engine->match(!!w->m_group))\n                    return false;\n                break;\n            case RULE_PROP_MODAL:\n                if (!engine->match(w->isModal()))\n                    return false;\n                break;\n            case RULE_PROP_FULLSCREENSTATE_INTERNAL:\n                if (!engine->match(w->m_fullscreenState.internal))\n                    return false;\n                break;\n            case RULE_PROP_FULLSCREENSTATE_CLIENT:\n                if (!engine->match(w->m_fullscreenState.client))\n                    return false;\n                break;\n            case RULE_PROP_ON_WORKSPACE:\n                if (!engine->match(w->m_workspace))\n                    return false;\n                break;\n            case RULE_PROP_CONTENT:\n                if (!engine->match(std::format(\"{}\", sc<size_t>(w->getContentType()))) && !engine->match(NContentType::toString(w->getContentType())))\n                    return false;\n                break;\n            case RULE_PROP_XDG_TAG:\n                if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag()))\n                    return false;\n                break;\n\n            case RULE_PROP_EXEC_TOKEN:\n                if (!allowEnvLookup)\n                    break;\n\n                const auto ENV   = w->getEnv();\n                bool       match = false;\n\n                if (ENV.contains(EXEC_RULE_ENV_NAME)) {\n                    if (engine->match(ENV.at(EXEC_RULE_ENV_NAME)))\n                        match = true;\n                } else if (m_matchEngines.contains(RULE_PROP_EXEC_PID)) {\n                    if (m_matchEngines.at(RULE_PROP_EXEC_PID)->match(w->getPID()))\n                        match = true;\n                }\n                if (!match)\n                    return false;\n                break;\n        }\n    }\n\n    return true;\n}\n\nSP<CWindowRule> CWindowRule::buildFromExecString(std::string&& s) {\n    CVarList2       varlist(std::move(s), 0, ';');\n    SP<CWindowRule> wr = makeShared<CWindowRule>(\"__exec_rule\");\n\n    for (const auto& el : varlist) {\n        // split element by space, can't do better\n        size_t spacePos = el.find(' ');\n        if (spacePos != std::string::npos) {\n            // great, split and try to parse\n            auto       LHS    = el.substr(0, spacePos);\n            const auto EFFECT = windowEffects()->get(LHS);\n\n            if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE)\n                continue; // invalid...\n\n            wr->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});\n            continue;\n        }\n\n        // assume 1 maybe...\n\n        const auto EFFECT = windowEffects()->get(el);\n\n        if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE)\n            continue; // invalid...\n\n        wr->addEffect(*EFFECT, std::string{\"1\"});\n    }\n\n    return wr;\n}\n\nconst std::unordered_set<CWindowRule::storageType>& CWindowRule::effectsSet() {\n    return m_effectSet;\n}\n"
  },
  {
    "path": "src/desktop/rule/windowRule/WindowRule.hpp",
    "content": "#pragma once\n\n#include \"../Rule.hpp\"\n#include \"../../DesktopTypes.hpp\"\n#include \"WindowRuleEffectContainer.hpp\"\n#include \"../../../helpers/math/Math.hpp\"\n\n#include <unordered_set>\n\nnamespace Desktop::Rule {\n    constexpr const char* EXEC_RULE_ENV_NAME = \"HL_EXEC_RULE_TOKEN\";\n\n    class CWindowRule : public IRule {\n      private:\n        using storageType = CWindowRuleEffectContainer::storageType;\n\n      public:\n        CWindowRule(const std::string& name = \"\");\n        virtual ~CWindowRule() = default;\n\n        static SP<CWindowRule>                                  buildFromExecString(std::string&&);\n\n        virtual eRuleType                                       type();\n\n        void                                                    addEffect(storageType e, const std::string& result);\n        const std::vector<std::pair<storageType, std::string>>& effects();\n        const std::unordered_set<storageType>&                  effectsSet();\n\n        bool                                                    matches(PHLWINDOW w, bool allowEnvLookup = false);\n\n      private:\n        std::vector<std::pair<storageType, std::string>> m_effects;\n        std::unordered_set<storageType>                  m_effectSet;\n    };\n};\n"
  },
  {
    "path": "src/desktop/rule/windowRule/WindowRuleApplicator.cpp",
    "content": "#include \"WindowRuleApplicator.hpp\"\n#include \"WindowRule.hpp\"\n#include \"../Engine.hpp\"\n#include \"../utils/SetUtils.hpp\"\n#include \"../../view/Window.hpp\"\n#include \"../../types/OverridableVar.hpp\"\n#include \"../../../event/EventBus.hpp\"\n\n#include <hyprutils/string/String.hpp>\n\nusing namespace Hyprutils::String;\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\nCWindowRuleApplicator::CWindowRuleApplicator(PHLWINDOW w) : m_window(w) {\n    ;\n}\n\nstd::unordered_set<CWindowRuleEffectContainer::storageType> CWindowRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio) {\n    // TODO: fucking kill me, is there a better way to do this?\n\n    std::unordered_set<CWindowRuleEffectContainer::storageType> effectsNuked;\n\n#define UNSET(x)                                                                                                                                                                   \\\n    if (m_##x.second & props) {                                                                                                                                                    \\\n        if (prio == Types::PRIORITY_WINDOW_RULE) {                                                                                                                                 \\\n            effectsNuked.emplace(x##Effect());                                                                                                                                     \\\n            m_##x.second &= ~props;                                                                                                                                                \\\n        }                                                                                                                                                                          \\\n        m_##x.first.unset(prio);                                                                                                                                                   \\\n    }\n\n    UNSET(alpha)\n    UNSET(alphaInactive)\n    UNSET(alphaFullscreen)\n    UNSET(allowsInput)\n    UNSET(decorate)\n    UNSET(focusOnActivate)\n    UNSET(keepAspectRatio)\n    UNSET(nearestNeighbor)\n    UNSET(noAnim)\n    UNSET(noBlur)\n    UNSET(noDim)\n    UNSET(noFocus)\n    UNSET(noMaxSize)\n    UNSET(noShadow)\n    UNSET(noShortcutsInhibit)\n    UNSET(opaque)\n    UNSET(dimAround)\n    UNSET(RGBX)\n    UNSET(syncFullscreen)\n    UNSET(tearing)\n    UNSET(xray)\n    UNSET(renderUnfocused)\n    UNSET(noFollowMouse)\n    UNSET(noScreenShare)\n    UNSET(noVRR)\n    UNSET(persistentSize)\n    UNSET(stayFocused)\n    UNSET(idleInhibitMode)\n    UNSET(borderSize)\n    UNSET(rounding)\n    UNSET(roundingPower)\n    UNSET(scrollMouse)\n    UNSET(scrollTouchpad)\n    UNSET(animationStyle)\n    UNSET(maxSize)\n    UNSET(minSize)\n    UNSET(activeBorderColor)\n    UNSET(inactiveBorderColor)\n\n#undef UNSET\n\n    if (prio == Types::PRIORITY_WINDOW_RULE) {\n        std::erase_if(m_dynamicTags, [props, this](const auto& el) {\n            const bool REMOVE = el.second & props;\n\n            if (REMOVE)\n                m_tagKeeper.removeDynamicTag(el.first);\n\n            return REMOVE;\n        });\n\n        std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; });\n    }\n\n    return effectsNuked;\n}\n\nCWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const SP<CWindowRule>& rule) {\n    SRuleResult result;\n\n    for (const auto& [key, effect] : rule->effects()) {\n        switch (key) {\n            default: {\n                if (key <= WINDOW_RULE_EFFECT_LAST_STATIC) {\n                    Log::logger->log(Log::TRACE, \"CWindowRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic\", sc<std::underlying_type_t<eWindowRuleEffect>>(key));\n                    break;\n                }\n\n                // custom type, add to our vec\n                if (!m_otherProps.props.contains(key)) {\n                    m_otherProps.props.emplace(key,\n                                               makeUnique<SCustomPropContainer>(SCustomPropContainer{\n                                                   .idx      = key,\n                                                   .propMask = rule->getPropertiesMask(),\n                                                   .effect   = effect,\n                                               }));\n                } else {\n                    auto& e = m_otherProps.props[key];\n                    e->propMask |= rule->getPropertiesMask();\n                    e->effect = effect;\n                }\n\n                break;\n            }\n\n            case WINDOW_RULE_EFFECT_NONE: {\n                Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyDynamicRule: BUG THIS: WINDOW_RULE_EFFECT_NONE??\");\n                break;\n            }\n            case WINDOW_RULE_EFFECT_ROUNDING: {\n                try {\n                    m_rounding.first.set(std::stoull(effect), Types::PRIORITY_WINDOW_RULE);\n                    m_rounding.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyDynamicRule: invalid rounding {}\", effect); }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_ROUNDING_POWER: {\n                try {\n                    m_roundingPower.first.set(std::clamp(std::stof(effect), 1.F, 10.F), Types::PRIORITY_WINDOW_RULE);\n                    m_roundingPower.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}\", effect); }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_PERSISTENT_SIZE: {\n                m_persistentSize.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_persistentSize.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_ANIMATION: {\n                m_animationStyle.first.set(effect, Types::PRIORITY_WINDOW_RULE);\n                m_animationStyle.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_BORDER_COLOR: {\n                try {\n                    // Each vector will only get used if it has at least one color\n                    CGradientValueData activeBorderGradient   = {};\n                    CGradientValueData inactiveBorderGradient = {};\n                    bool               active                 = true;\n                    CVarList           colorsAndAngles        = CVarList(trim(effect), 0, 's', true);\n\n                    // Basic form has only two colors, everything else can be parsed as a gradient\n                    if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains(\"deg\")) {\n                        m_activeBorderColor.first =\n                            Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE);\n                        m_inactiveBorderColor.first =\n                            Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE);\n                        m_activeBorderColor.second |= rule->getPropertiesMask();\n                        m_inactiveBorderColor.second |= rule->getPropertiesMask();\n                        break;\n                    }\n\n                    for (auto const& token : colorsAndAngles) {\n                        // The first angle, or an explicit \"0deg\", splits the two gradients\n                        if (active && token.contains(\"deg\")) {\n                            activeBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);\n                            active                       = false;\n                        } else if (token.contains(\"deg\"))\n                            inactiveBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);\n                        else if (active)\n                            activeBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0));\n                        else\n                            inactiveBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0));\n                    }\n\n                    activeBorderGradient.updateColorsOk();\n\n                    // Includes sanity checks for the number of colors in each gradient\n                    if (activeBorderGradient.m_colors.size() > 10 || inactiveBorderGradient.m_colors.size() > 10)\n                        Log::logger->log(Log::WARN, \"Bordercolor rule \\\"{}\\\" has more than 10 colors in one gradient, ignoring\", effect);\n                    else if (activeBorderGradient.m_colors.empty())\n                        Log::logger->log(Log::WARN, \"Bordercolor rule \\\"{}\\\" has no colors, ignoring\", effect);\n                    else if (inactiveBorderGradient.m_colors.empty())\n                        m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE);\n                    else {\n                        m_activeBorderColor.first   = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE);\n                        m_inactiveBorderColor.first = Types::COverridableVar(inactiveBorderGradient, Types::PRIORITY_WINDOW_RULE);\n                    }\n                } catch (std::exception& e) { Log::logger->log(Log::ERR, \"BorderColor rule \\\"{}\\\" failed with: {}\", effect, e.what()); }\n                m_activeBorderColor.second   = rule->getPropertiesMask();\n                m_inactiveBorderColor.second = rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_IDLE_INHIBIT: {\n                if (effect == \"none\")\n                    m_idleInhibitMode.first.set(IDLEINHIBIT_NONE, Types::PRIORITY_WINDOW_RULE);\n                else if (effect == \"always\")\n                    m_idleInhibitMode.first.set(IDLEINHIBIT_ALWAYS, Types::PRIORITY_WINDOW_RULE);\n                else if (effect == \"focus\")\n                    m_idleInhibitMode.first.set(IDLEINHIBIT_FOCUS, Types::PRIORITY_WINDOW_RULE);\n                else if (effect == \"fullscreen\")\n                    m_idleInhibitMode.first.set(IDLEINHIBIT_FULLSCREEN, Types::PRIORITY_WINDOW_RULE);\n                else\n                    Log::logger->log(Log::ERR, \"Rule idleinhibit: unknown mode {}\", effect);\n                m_idleInhibitMode.second = rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_OPACITY: {\n                try {\n                    CVarList2 vars(std::string{effect}, 0, ' ');\n\n                    int       opacityIDX = 0;\n\n                    for (const auto& r : vars) {\n                        if (r == \"opacity\")\n                            continue;\n\n                        if (r == \"override\") {\n                            if (opacityIDX == 1)\n                                m_alpha.first = Types::COverridableVar(Types::SAlphaValue{.alpha = m_alpha.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE);\n                            else if (opacityIDX == 2)\n                                m_alphaInactive.first =\n                                    Types::COverridableVar(Types::SAlphaValue{.alpha = m_alphaInactive.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE);\n                            else if (opacityIDX == 3)\n                                m_alphaFullscreen.first =\n                                    Types::COverridableVar(Types::SAlphaValue{.alpha = m_alphaFullscreen.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE);\n                        } else {\n                            if (opacityIDX == 0)\n                                m_alpha.first = Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE);\n                            else if (opacityIDX == 1)\n                                m_alphaInactive.first =\n                                    Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE);\n                            else if (opacityIDX == 2)\n                                m_alphaFullscreen.first =\n                                    Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE);\n                            else\n                                throw std::runtime_error(\"more than 3 alpha values\");\n\n                            opacityIDX++;\n                        }\n                    }\n\n                    if (opacityIDX == 1) {\n                        m_alphaInactive.first   = m_alpha.first;\n                        m_alphaFullscreen.first = m_alpha.first;\n                    }\n                } catch (std::exception& e) { Log::logger->log(Log::ERR, \"Opacity rule \\\"{}\\\" failed with: {}\", effect, e.what()); }\n                m_alpha.second           = rule->getPropertiesMask();\n                m_alphaInactive.second   = rule->getPropertiesMask();\n                m_alphaFullscreen.second = rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_TAG: {\n                m_dynamicTags.emplace_back(std::make_pair<>(effect, rule->getPropertiesMask()));\n                m_tagKeeper.applyTag(effect, true);\n                result.tagsChanged = true;\n                break;\n            }\n            case WINDOW_RULE_EFFECT_MAX_SIZE: {\n                try {\n                    static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>(\"misc:size_limits_tiled\");\n\n                    if (!m_window)\n                        break;\n\n                    const auto VEC = m_window->calculateExpression(effect);\n                    if (!VEC) {\n                        Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", effect);\n                        break;\n                    }\n                    if (VEC->x < 1 || VEC->y < 1) {\n                        Log::logger->log(Log::ERR, \"Invalid size for maxsize\");\n                        break;\n                    }\n\n                    m_maxSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE);\n\n                    if (*PCLAMP_TILED || m_window->m_isFloating)\n                        m_window->clampWindowSize(std::nullopt, m_maxSize.first.value());\n                } catch (std::exception& e) { Log::logger->log(Log::ERR, \"maxsize rule \\\"{}\\\" failed with: {}\", effect, e.what()); }\n                m_maxSize.second = rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_MIN_SIZE: {\n                try {\n                    static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>(\"misc:size_limits_tiled\");\n\n                    if (!m_window)\n                        break;\n\n                    const auto VEC = m_window->calculateExpression(effect);\n                    if (!VEC) {\n                        Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", effect);\n                        break;\n                    }\n\n                    if (VEC->x < 1 || VEC->y < 1) {\n                        Log::logger->log(Log::ERR, \"Invalid size for maxsize\");\n                        break;\n                    }\n\n                    m_minSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE);\n                    if (*PCLAMP_TILED || m_window->m_isFloating)\n                        m_window->clampWindowSize(m_minSize.first.value(), std::nullopt);\n                } catch (std::exception& e) { Log::logger->log(Log::ERR, \"minsize rule \\\"{}\\\" failed with: {}\", effect, e.what()); }\n                m_minSize.second = rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_BORDER_SIZE: {\n                try {\n                    auto oldBorderSize = m_borderSize.first.valueOrDefault();\n                    m_borderSize.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);\n                    m_borderSize.second |= rule->getPropertiesMask();\n                    if (oldBorderSize != m_borderSize.first.valueOrDefault())\n                        result.needsRelayout = true;\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyDynamicRule: invalid border_size {}\", effect); }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_ALLOWS_INPUT: {\n                m_allowsInput.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_allowsInput.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_DIM_AROUND: {\n                m_dimAround.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_dimAround.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_DECORATE: {\n                m_decorate.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_decorate.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE: {\n                m_focusOnActivate.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_focusOnActivate.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO: {\n                m_keepAspectRatio.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_keepAspectRatio.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR: {\n                m_nearestNeighbor.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_nearestNeighbor.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_ANIM: {\n                m_noAnim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noAnim.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_BLUR: {\n                m_noBlur.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noBlur.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_DIM: {\n                m_noDim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noDim.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_FOCUS: {\n                m_noFocus.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noFocus.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE: {\n                m_noFollowMouse.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noFollowMouse.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_MAX_SIZE: {\n                m_noMaxSize.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noMaxSize.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_SHADOW: {\n                m_noShadow.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noShadow.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT: {\n                m_noShortcutsInhibit.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noShortcutsInhibit.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_OPAQUE: {\n                m_opaque.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_opaque.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_FORCE_RGBX: {\n                m_RGBX.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_RGBX.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_SYNC_FULLSCREEN: {\n                m_syncFullscreen.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_syncFullscreen.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_IMMEDIATE: {\n                m_tearing.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_tearing.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_XRAY: {\n                m_xray.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_xray.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_RENDER_UNFOCUSED: {\n                m_renderUnfocused.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_renderUnfocused.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_SCREEN_SHARE: {\n                m_noScreenShare.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noScreenShare.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NO_VRR: {\n                m_noVRR.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_noVRR.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_STAY_FOCUSED: {\n                m_stayFocused.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);\n                m_stayFocused.second |= rule->getPropertiesMask();\n                break;\n            }\n            case WINDOW_RULE_EFFECT_SCROLL_MOUSE: {\n                try {\n                    m_scrollMouse.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE);\n                    m_scrollMouse.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyDynamicRule: invalid scroll_mouse {}\", effect); }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD: {\n                try {\n                    m_scrollTouchpad.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE);\n                    m_scrollTouchpad.second |= rule->getPropertiesMask();\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyDynamicRule: invalid scroll_touchpad {}\", effect); }\n                break;\n            }\n        }\n    }\n    return result;\n}\n\nCWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const SP<CWindowRule>& rule) {\n    for (const auto& [key, effect] : rule->effects()) {\n        switch (key) {\n            default: {\n                Log::logger->log(Log::TRACE, \"CWindowRuleApplicator::applyStaticRule: Skipping effect {}, not static\", sc<std::underlying_type_t<eWindowRuleEffect>>(key));\n                break;\n            }\n\n            case WINDOW_RULE_EFFECT_FLOAT: {\n                static_.floating = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_TILE: {\n                static_.floating = !truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_FULLSCREEN: {\n                static_.fullscreen = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_MAXIMIZE: {\n                static_.maximize = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_FULLSCREENSTATE: {\n                CVarList2 vars(std::string{effect}, 0, 's');\n                try {\n                    static_.fullscreenStateInternal = std::stoi(std::string{vars[0]});\n                    if (!vars[1].empty())\n                        static_.fullscreenStateClient = std::stoi(std::string{vars[1]});\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyStaticRule: invalid fullscreen state {}\", effect); }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_MOVE: {\n                static_.center   = std::nullopt;\n                static_.position = effect;\n                break;\n            }\n            case WINDOW_RULE_EFFECT_SIZE: {\n                static_.size = effect;\n                break;\n            }\n            case WINDOW_RULE_EFFECT_CENTER: {\n                static_.position.clear();\n                static_.center = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_PSEUDO: {\n                static_.pseudo = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_MONITOR: {\n                static_.monitor = effect;\n                break;\n            }\n            case WINDOW_RULE_EFFECT_WORKSPACE: {\n                static_.workspace = effect;\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NOINITIALFOCUS: {\n                static_.noInitialFocus = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_PIN: {\n                static_.pin = truthy(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_GROUP: {\n                static_.group = effect;\n                break;\n            }\n            case WINDOW_RULE_EFFECT_SUPPRESSEVENT: {\n                CVarList2 varlist(std::string{effect}, 0, 's');\n                for (const auto& e : varlist) {\n                    static_.suppressEvent.emplace_back(e);\n                }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_CONTENT: {\n                static_.content = NContentType::fromString(effect);\n                break;\n            }\n            case WINDOW_RULE_EFFECT_NOCLOSEFOR: {\n                try {\n                    static_.noCloseFor = std::stoi(effect);\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyStaticRule: invalid no close for {}\", effect); }\n                break;\n            }\n            case WINDOW_RULE_EFFECT_SCROLLING_WIDTH: {\n                try {\n                    static_.scrollingWidth = std::stof(effect);\n                } catch (...) { Log::logger->log(Log::ERR, \"CWindowRuleApplicator::applyStaticRule: invalid scrolling width {}\", effect); }\n                break;\n            }\n        }\n    }\n\n    return SRuleResult{};\n}\n\n//\nbool CWindowRuleApplicator::readStaticRules(bool preRead) {\n    if (!m_window)\n        return false;\n\n    static_ = {};\n\n    std::vector<SP<CWindowRule>> execRules;\n    bool                         tagsWereChanged = false;\n\n    for (const auto& r : ruleEngine()->rules()) {\n        if (r->type() != RULE_TYPE_WINDOW)\n            continue;\n\n        auto wr = reinterpretPointerCast<CWindowRule>(r);\n\n        if (!wr->matches(m_window.lock(), true))\n            continue;\n\n        if (wr->isExecRule()) {\n            execRules.emplace_back(wr);\n            continue;\n        }\n\n        applyStaticRule(wr);\n        const auto RES  = applyDynamicRule(wr);\n        tagsWereChanged = tagsWereChanged || RES.tagsChanged;\n    }\n\n    // set a recheck for some props people might wanna use for static rules.\n    if (tagsWereChanged)\n        propsToRecheck |= RULE_PROP_TAG;\n    if (static_.content != NContentType::CONTENT_TYPE_NONE)\n        propsToRecheck |= RULE_PROP_CONTENT;\n\n    for (const auto& wr : execRules) {\n        applyStaticRule(wr);\n        applyDynamicRule(wr);\n        if (!preRead)\n            ruleEngine()->unregisterRule(wr);\n    }\n    return (propsToRecheck != RULE_PROP_NONE);\n}\n\nvoid CWindowRuleApplicator::recheckStaticRules() {\n    for (const auto& r : ruleEngine()->rules()) {\n        if (r->type() != RULE_TYPE_WINDOW)\n            continue;\n\n        if (!(r->getPropertiesMask() & propsToRecheck))\n            continue;\n\n        auto wr = reinterpretPointerCast<CWindowRule>(r);\n\n        if (!wr->matches(m_window.lock(), true))\n            continue;\n\n        applyStaticRule(wr);\n    }\n}\n\nvoid CWindowRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProperty> props) {\n    if (!m_window || !m_window->m_isMapped || m_window->isHidden())\n        return;\n\n    bool                                                        needsRelayout         = false;\n    std::unordered_set<CWindowRuleEffectContainer::storageType> effectsNeedingRecheck = resetProps(props);\n\n    for (const auto& r : ruleEngine()->rules()) {\n        if (r->type() != RULE_TYPE_WINDOW)\n            continue;\n\n        const auto WR = reinterpretPointerCast<CWindowRule>(r);\n\n        if (!(WR->getPropertiesMask() & props) && !setsIntersect(WR->effectsSet(), effectsNeedingRecheck))\n            continue;\n\n        if (!WR->matches(m_window.lock()))\n            continue;\n\n        const auto RES = applyDynamicRule(WR);\n        needsRelayout  = needsRelayout || RES.needsRelayout;\n    }\n\n    m_window->updateWindowData();\n    m_window->updateWindowDecos();\n    m_window->updateDecorationValues();\n\n    if (needsRelayout)\n        g_pDecorationPositioner->forceRecalcFor(m_window.lock());\n\n    // for plugins\n    Event::bus()->m_events.window.updateRules.emit(m_window.lock());\n}\n"
  },
  {
    "path": "src/desktop/rule/windowRule/WindowRuleApplicator.hpp",
    "content": "#pragma once\n\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"WindowRuleEffectContainer.hpp\"\n#include \"../../DesktopTypes.hpp\"\n#include \"../Rule.hpp\"\n#include \"../../types/OverridableVar.hpp\"\n#include \"../../../helpers/math/Math.hpp\"\n#include \"../../../helpers/TagKeeper.hpp\"\n#include \"../../../config/ConfigDataValues.hpp\"\n\nnamespace Desktop::Rule {\n    class CWindowRule;\n\n    enum eIdleInhibitMode : uint8_t {\n        IDLEINHIBIT_NONE = 0,\n        IDLEINHIBIT_ALWAYS,\n        IDLEINHIBIT_FULLSCREEN,\n        IDLEINHIBIT_FOCUS\n    };\n\n    class CWindowRuleApplicator {\n      public:\n        CWindowRuleApplicator(PHLWINDOW w);\n        ~CWindowRuleApplicator() = default;\n\n        CWindowRuleApplicator(const CWindowRuleApplicator&) = delete;\n        CWindowRuleApplicator(CWindowRuleApplicator&)       = delete;\n        CWindowRuleApplicator(CWindowRuleApplicator&&)      = delete;\n\n        void                                                        propertiesChanged(std::underlying_type_t<eRuleProperty> props);\n        std::unordered_set<CWindowRuleEffectContainer::storageType> resetProps(std::underlying_type_t<eRuleProperty> props,\n                                                                               Types::eOverridePriority              prio = Types::PRIORITY_WINDOW_RULE);\n        bool                                                        readStaticRules(bool preRead = false);\n        void                                                        recheckStaticRules();\n\n        // static props\n        struct {\n            std::string              monitor, workspace, group;\n\n            std::optional<bool>      floating;\n            std::optional<bool>      fullscreen;\n            std::optional<bool>      maximize;\n            std::optional<bool>      pseudo;\n            std::optional<bool>      pin;\n            std::optional<bool>      noInitialFocus;\n            std::optional<bool>      center;\n\n            std::optional<int>       fullscreenStateClient;\n            std::optional<int>       fullscreenStateInternal;\n            std::optional<int>       content;\n            std::optional<int>       noCloseFor;\n\n            std::string              size, position;\n            std::optional<float>     scrollingWidth;\n\n            std::vector<std::string> suppressEvent;\n        } static_;\n\n        struct SCustomPropContainer {\n            CWindowRuleEffectContainer::storageType idx      = WINDOW_RULE_EFFECT_NONE;\n            std::underlying_type_t<eRuleProperty>   propMask = RULE_PROP_NONE;\n            std::string                             effect;\n        };\n\n        // This struct holds props that were dynamically registered. Plugins may read this.\n        struct {\n            std::unordered_map<CWindowRuleEffectContainer::storageType, UP<SCustomPropContainer>> props;\n        } m_otherProps;\n\n#define COMMA ,\n#define DEFINE_PROP(type, name, def, eff)                                                                                                                                          \\\n  private:                                                                                                                                                                         \\\n    std::pair<Types::COverridableVar<type>, std::underlying_type_t<eRuleProperty>> m_##name = {def, RULE_PROP_NONE};                                                               \\\n                                                                                                                                                                                   \\\n  public:                                                                                                                                                                          \\\n    Types::COverridableVar<type>& name() {                                                                                                                                         \\\n        return m_##name.first;                                                                                                                                                     \\\n    }                                                                                                                                                                              \\\n    void name##Override(const Types::COverridableVar<type>& other) {                                                                                                               \\\n        m_##name.first = other;                                                                                                                                                    \\\n    }                                                                                                                                                                              \\\n    eWindowRuleEffect name##Effect() {                                                                                                                                             \\\n        return eff;                                                                                                                                                                \\\n    }\n\n        // dynamic props\n        DEFINE_PROP(Types::SAlphaValue, alpha, Types::SAlphaValue{}, WINDOW_RULE_EFFECT_OPACITY)\n        DEFINE_PROP(Types::SAlphaValue, alphaInactive, Types::SAlphaValue{}, WINDOW_RULE_EFFECT_OPACITY)\n        DEFINE_PROP(Types::SAlphaValue, alphaFullscreen, Types::SAlphaValue{}, WINDOW_RULE_EFFECT_OPACITY)\n\n        DEFINE_PROP(bool, allowsInput, false, WINDOW_RULE_EFFECT_ALLOWS_INPUT)\n        DEFINE_PROP(bool, decorate, true, WINDOW_RULE_EFFECT_DECORATE)\n        DEFINE_PROP(bool, focusOnActivate, false, WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE)\n        DEFINE_PROP(bool, keepAspectRatio, false, WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO)\n        DEFINE_PROP(bool, nearestNeighbor, false, WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR)\n        DEFINE_PROP(bool, noAnim, false, WINDOW_RULE_EFFECT_NO_ANIM)\n        DEFINE_PROP(bool, noBlur, false, WINDOW_RULE_EFFECT_NO_BLUR)\n        DEFINE_PROP(bool, noDim, false, WINDOW_RULE_EFFECT_NO_DIM)\n        DEFINE_PROP(bool, noFocus, false, WINDOW_RULE_EFFECT_NO_FOCUS)\n        DEFINE_PROP(bool, noMaxSize, false, WINDOW_RULE_EFFECT_NO_MAX_SIZE)\n        DEFINE_PROP(bool, noShadow, false, WINDOW_RULE_EFFECT_NO_SHADOW)\n        DEFINE_PROP(bool, noShortcutsInhibit, false, WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT)\n        DEFINE_PROP(bool, opaque, false, WINDOW_RULE_EFFECT_OPAQUE)\n        DEFINE_PROP(bool, dimAround, false, WINDOW_RULE_EFFECT_DIM_AROUND)\n        DEFINE_PROP(bool, RGBX, false, WINDOW_RULE_EFFECT_FORCE_RGBX)\n        DEFINE_PROP(bool, syncFullscreen, true, WINDOW_RULE_EFFECT_SYNC_FULLSCREEN)\n        DEFINE_PROP(bool, tearing, false, WINDOW_RULE_EFFECT_IMMEDIATE)\n        DEFINE_PROP(bool, xray, false, WINDOW_RULE_EFFECT_XRAY)\n        DEFINE_PROP(bool, renderUnfocused, false, WINDOW_RULE_EFFECT_RENDER_UNFOCUSED)\n        DEFINE_PROP(bool, noFollowMouse, false, WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE)\n        DEFINE_PROP(bool, noScreenShare, false, WINDOW_RULE_EFFECT_NO_SCREEN_SHARE)\n        DEFINE_PROP(bool, noVRR, false, WINDOW_RULE_EFFECT_NO_VRR)\n        DEFINE_PROP(bool, persistentSize, false, WINDOW_RULE_EFFECT_PERSISTENT_SIZE)\n        DEFINE_PROP(bool, stayFocused, false, WINDOW_RULE_EFFECT_STAY_FOCUSED)\n\n        DEFINE_PROP(int, idleInhibitMode, false, WINDOW_RULE_EFFECT_IDLE_INHIBIT)\n\n        DEFINE_PROP(Hyprlang::INT, borderSize, {std::string(\"general:border_size\") COMMA sc<Hyprlang::INT>(0) COMMA std::nullopt}, WINDOW_RULE_EFFECT_BORDER_SIZE)\n        DEFINE_PROP(Hyprlang::INT, rounding, {std::string(\"decoration:rounding\") COMMA sc<Hyprlang::INT>(0) COMMA std::nullopt}, WINDOW_RULE_EFFECT_ROUNDING)\n\n        DEFINE_PROP(Hyprlang::FLOAT, roundingPower, {std::string(\"decoration:rounding_power\")}, WINDOW_RULE_EFFECT_ROUNDING_POWER)\n        DEFINE_PROP(Hyprlang::FLOAT, scrollMouse, {std::string(\"input:scroll_factor\")}, WINDOW_RULE_EFFECT_SCROLL_MOUSE)\n        DEFINE_PROP(Hyprlang::FLOAT, scrollTouchpad, {std::string(\"input:touchpad:scroll_factor\")}, WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD)\n\n        DEFINE_PROP(std::string, animationStyle, std::string(\"\"), WINDOW_RULE_EFFECT_ANIMATION)\n\n        DEFINE_PROP(Vector2D, maxSize, Vector2D{}, WINDOW_RULE_EFFECT_MAX_SIZE)\n        DEFINE_PROP(Vector2D, minSize, Vector2D{}, WINDOW_RULE_EFFECT_MIN_SIZE)\n\n        DEFINE_PROP(CGradientValueData, activeBorderColor, {}, WINDOW_RULE_EFFECT_BORDER_COLOR)\n        DEFINE_PROP(CGradientValueData, inactiveBorderColor, {}, WINDOW_RULE_EFFECT_BORDER_COLOR)\n\n        std::vector<std::pair<std::string, std::underlying_type_t<eRuleProperty>>> m_dynamicTags;\n        CTagKeeper                                                                 m_tagKeeper;\n\n#undef COMMA\n#undef DEFINE_PROP\n\n      private:\n        PHLWINDOWREF                          m_window;\n        std::underlying_type_t<eRuleProperty> propsToRecheck = RULE_PROP_NONE;\n\n        struct SRuleResult {\n            bool needsRelayout = false;\n            bool tagsChanged   = false;\n        };\n\n        SRuleResult applyDynamicRule(const SP<CWindowRule>& rule);\n        SRuleResult applyStaticRule(const SP<CWindowRule>& rule);\n    };\n};\n"
  },
  {
    "path": "src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp",
    "content": "#include \"WindowRuleEffectContainer.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::Rule;\n\n//\nSP<CWindowRuleEffectContainer> Rule::windowEffects() {\n    static SP<CWindowRuleEffectContainer> container = makeShared<CWindowRuleEffectContainer>();\n    return container;\n}\n\nstatic const std::vector<std::string> EFFECT_STRINGS = {\n    \"__internal_none\",        //\n    \"float\",                  //\n    \"tile\",                   //\n    \"fullscreen\",             //\n    \"maximize\",               //\n    \"fullscreen_state\",       //\n    \"move\",                   //\n    \"size\",                   //\n    \"center\",                 //\n    \"pseudo\",                 //\n    \"monitor\",                //\n    \"workspace\",              //\n    \"no_initial_focus\",       //\n    \"pin\",                    //\n    \"group\",                  //\n    \"suppress_event\",         //\n    \"content\",                //\n    \"no_close_for\",           //\n    \"scrolling_width\",        //\n    \"rounding\",               //\n    \"rounding_power\",         //\n    \"persistent_size\",        //\n    \"animation\",              //\n    \"border_color\",           //\n    \"idle_inhibit\",           //\n    \"opacity\",                //\n    \"tag\",                    //\n    \"max_size\",               //\n    \"min_size\",               //\n    \"border_size\",            //\n    \"allows_input\",           //\n    \"dim_around\",             //\n    \"decorate\",               //\n    \"focus_on_activate\",      //\n    \"keep_aspect_ratio\",      //\n    \"nearest_neighbor\",       //\n    \"no_anim\",                //\n    \"no_blur\",                //\n    \"no_dim\",                 //\n    \"no_focus\",               //\n    \"no_follow_mouse\",        //\n    \"no_max_size\",            //\n    \"no_shadow\",              //\n    \"no_shortcuts_inhibit\",   //\n    \"opaque\",                 //\n    \"force_rgbx\",             //\n    \"sync_fullscreen\",        //\n    \"immediate\",              //\n    \"xray\",                   //\n    \"render_unfocused\",       //\n    \"no_screen_share\",        //\n    \"no_vrr\",                 //\n    \"scroll_mouse\",           //\n    \"scroll_touchpad\",        //\n    \"stay_focused\",           //\n    \"__internal_last_static\", //\n};\n\n// This is here so that if we change the rules, we get reminded to update\n// the strings.\nstatic_assert(WINDOW_RULE_EFFECT_LAST_STATIC == 55);\n\nCWindowRuleEffectContainer::CWindowRuleEffectContainer() : IEffectContainer<eWindowRuleEffect>(std::vector<std::string>{EFFECT_STRINGS}) {\n    ;\n}\n"
  },
  {
    "path": "src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp",
    "content": "#pragma once\n\n#include \"../effect/EffectContainer.hpp\"\n#include \"../../../helpers/memory/Memory.hpp\"\n\n#pragma once\n\nnamespace Desktop::Rule {\n    enum eWindowRuleEffect : uint8_t {\n        WINDOW_RULE_EFFECT_NONE = 0,\n\n        // static\n        WINDOW_RULE_EFFECT_FLOAT,\n        WINDOW_RULE_EFFECT_TILE,\n        WINDOW_RULE_EFFECT_FULLSCREEN,\n        WINDOW_RULE_EFFECT_MAXIMIZE,\n        WINDOW_RULE_EFFECT_FULLSCREENSTATE,\n        WINDOW_RULE_EFFECT_MOVE,\n        WINDOW_RULE_EFFECT_SIZE,\n        WINDOW_RULE_EFFECT_CENTER,\n        WINDOW_RULE_EFFECT_PSEUDO,\n        WINDOW_RULE_EFFECT_MONITOR,\n        WINDOW_RULE_EFFECT_WORKSPACE,\n        WINDOW_RULE_EFFECT_NOINITIALFOCUS,\n        WINDOW_RULE_EFFECT_PIN,\n        WINDOW_RULE_EFFECT_GROUP,\n        WINDOW_RULE_EFFECT_SUPPRESSEVENT,\n        WINDOW_RULE_EFFECT_CONTENT,\n        WINDOW_RULE_EFFECT_NOCLOSEFOR,\n        WINDOW_RULE_EFFECT_SCROLLING_WIDTH,\n\n        // dynamic\n        WINDOW_RULE_EFFECT_ROUNDING,\n        WINDOW_RULE_EFFECT_ROUNDING_POWER,\n        WINDOW_RULE_EFFECT_PERSISTENT_SIZE,\n        WINDOW_RULE_EFFECT_ANIMATION,\n        WINDOW_RULE_EFFECT_BORDER_COLOR,\n        WINDOW_RULE_EFFECT_IDLE_INHIBIT,\n        WINDOW_RULE_EFFECT_OPACITY,\n        WINDOW_RULE_EFFECT_TAG,\n        WINDOW_RULE_EFFECT_MAX_SIZE,\n        WINDOW_RULE_EFFECT_MIN_SIZE,\n        WINDOW_RULE_EFFECT_BORDER_SIZE,\n        WINDOW_RULE_EFFECT_ALLOWS_INPUT,\n        WINDOW_RULE_EFFECT_DIM_AROUND,\n        WINDOW_RULE_EFFECT_DECORATE,\n        WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE,\n        WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO,\n        WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR,\n        WINDOW_RULE_EFFECT_NO_ANIM,\n        WINDOW_RULE_EFFECT_NO_BLUR,\n        WINDOW_RULE_EFFECT_NO_DIM,\n        WINDOW_RULE_EFFECT_NO_FOCUS,\n        WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE,\n        WINDOW_RULE_EFFECT_NO_MAX_SIZE,\n        WINDOW_RULE_EFFECT_NO_SHADOW,\n        WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT,\n        WINDOW_RULE_EFFECT_OPAQUE,\n        WINDOW_RULE_EFFECT_FORCE_RGBX,\n        WINDOW_RULE_EFFECT_SYNC_FULLSCREEN,\n        WINDOW_RULE_EFFECT_IMMEDIATE,\n        WINDOW_RULE_EFFECT_XRAY,\n        WINDOW_RULE_EFFECT_RENDER_UNFOCUSED,\n        WINDOW_RULE_EFFECT_NO_SCREEN_SHARE,\n        WINDOW_RULE_EFFECT_NO_VRR,\n        WINDOW_RULE_EFFECT_SCROLL_MOUSE,\n        WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD,\n        WINDOW_RULE_EFFECT_STAY_FOCUSED,\n\n        WINDOW_RULE_EFFECT_LAST_STATIC,\n    };\n\n    class CWindowRuleEffectContainer : public IEffectContainer<eWindowRuleEffect> {\n      public:\n        CWindowRuleEffectContainer();\n        virtual ~CWindowRuleEffectContainer() = default;\n    };\n\n    SP<CWindowRuleEffectContainer> windowEffects();\n};\n"
  },
  {
    "path": "src/desktop/state/FocusState.cpp",
    "content": "#include \"FocusState.hpp\"\n#include \"../view/Window.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../protocols/XDGShell.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../managers/EventManager.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../xwayland/XSurface.hpp\"\n#include \"../../protocols/PointerConstraints.hpp\"\n#include \"managers/animation/DesktopAnimationManager.hpp\"\n#include \"../../layout/LayoutManager.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nusing namespace Desktop;\n\n#define COMMA ,\n\nSP<CFocusState> Desktop::focusState() {\n    static SP<CFocusState> state = makeShared<CFocusState>();\n    return state;\n}\n\nDesktop::CFocusState::CFocusState() = default;\n\nstruct SFullscreenWorkspaceFocusResult {\n    PHLWINDOW overrideFocusWindow = nullptr;\n};\n\nstatic SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDOW pWindow, bool forceFSCycle) {\n    const auto FSWINDOW = pWindow->m_workspace->getFullscreenWindow();\n    const auto FSMODE   = pWindow->m_workspace->m_fullscreenMode;\n\n    if (pWindow == FSWINDOW)\n        return {}; // no conflict\n\n    if (pWindow->m_isFloating) {\n        // if the window is floating, just bring it to the top\n        pWindow->m_createdOverFullscreen = true;\n        g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f);\n        g_pHyprRenderer->damageWindow(pWindow);\n        return {};\n    }\n\n    static auto PONFOCUSUNDERFS = CConfigValue<Hyprlang::INT>(\"misc:on_focus_under_fullscreen\");\n\n    switch (*PONFOCUSUNDERFS) {\n        case 0:\n            // focus the fullscreen window instead\n            return {.overrideFocusWindow = FSWINDOW};\n        case 2:\n            // undo fs, unless we force a cycle\n            if (!forceFSCycle) {\n                g_pCompositor->setWindowFullscreenInternal(FSWINDOW, FSMODE_NONE);\n                break;\n            }\n            [[fallthrough]];\n        case 1:\n            // replace fullscreen\n            g_pCompositor->setWindowFullscreenInternal(FSWINDOW, FSMODE_NONE);\n            g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE);\n            break;\n\n        default: Log::logger->log(Log::ERR, \"Invalid misc:on_focus_under_fullscreen mode: {}\", *PONFOCUSUNDERFS); break;\n    }\n\n    return {};\n}\n\nvoid CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface, bool forceFSCycle) {\n    if (pWindow) {\n        if (!pWindow->m_workspace)\n            return;\n\n        const auto CURRENT_FS_MODE = pWindow->m_workspace->m_hasFullscreenWindow ? pWindow->m_workspace->m_fullscreenMode : FSMODE_NONE;\n        if (CURRENT_FS_MODE != FSMODE_NONE) {\n            const auto RESULT = onFullscreenWorkspaceFocusWindow(pWindow, forceFSCycle);\n            if (RESULT.overrideFocusWindow)\n                pWindow = RESULT.overrideFocusWindow;\n        }\n    }\n\n    static auto PMODALPARENTBLOCKING = CConfigValue<Hyprlang::INT>(\"general:modal_parent_blocking\");\n\n    if (*PMODALPARENTBLOCKING && pWindow && pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel && pWindow->m_xdgSurface->m_toplevel->anyChildModal()) {\n        Log::logger->log(Log::DEBUG, \"Refusing focus to window shadowed by modal dialog\");\n        return;\n    }\n\n    rawWindowFocus(pWindow, reason, surface);\n}\n\nvoid CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface) {\n    static auto PFOLLOWMOUSE        = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n    static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>(\"input:special_fallthrough\");\n\n    if (!pWindow || !pWindow->priorityFocus()) {\n        if (g_pSessionLockManager->isSessionLocked()) {\n            Log::logger->log(Log::DEBUG, \"Refusing a keyboard focus to a window because of a sessionlock\");\n            return;\n        }\n\n        if (!g_pInputManager->m_exclusiveLSes.empty()) {\n            Log::logger->log(Log::DEBUG, \"Refusing a keyboard focus to a window because of an exclusive ls\");\n            return;\n        }\n    }\n\n    if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus())\n        return;\n\n    // m_target on purpose, this avoids the group\n    if (pWindow)\n        g_layoutManager->bringTargetToTop(pWindow->m_target);\n\n    if (!pWindow || !validMapped(pWindow)) {\n\n        if (m_focusWindow.expired() && !pWindow)\n            return;\n\n        const auto PLASTWINDOW = m_focusWindow.lock();\n        m_focusWindow.reset();\n\n        if (PLASTWINDOW && PLASTWINDOW->m_isMapped) {\n            PLASTWINDOW->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS);\n            PLASTWINDOW->updateDecorationValues();\n\n            g_pXWaylandManager->activateWindow(PLASTWINDOW, false);\n        }\n\n        g_pSeatManager->setKeyboardFocus(nullptr);\n\n        g_pEventManager->postEvent(SHyprIPCEvent{\"activewindow\", \",\"});\n        g_pEventManager->postEvent(SHyprIPCEvent{\"activewindowv2\", \"\"});\n\n        Event::bus()->m_events.window.active.emit(nullptr, reason);\n\n        m_focusSurface.reset();\n\n        g_pInputManager->recheckIdleInhibitorStatus();\n        return;\n    }\n\n    if (pWindow->m_ruleApplicator->noFocus().valueOrDefault()) {\n        Log::logger->log(Log::DEBUG, \"Ignoring focus to nofocus window!\");\n        return;\n    }\n\n    if (m_focusWindow.lock() == pWindow && g_pSeatManager->m_state.keyboardFocus == surface && g_pSeatManager->m_state.keyboardFocus)\n        return;\n\n    if (pWindow->m_pinned)\n        pWindow->m_workspace = m_focusMonitor->m_activeWorkspace;\n\n    const auto PMONITOR = pWindow->m_monitor.lock();\n\n    if (!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) {\n        const auto PWORKSPACE = pWindow->m_workspace;\n        // This is to fix incorrect feedback on the focus history.\n        PWORKSPACE->m_lastFocusedWindow = pWindow;\n        if (PWORKSPACE->m_isSpecialWorkspace)\n            m_focusMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor\n        else if (PMONITOR)\n            PMONITOR->changeWorkspace(PWORKSPACE, false, true);\n        // changeworkspace already calls focusWindow\n        return;\n    }\n\n    const auto PLASTWINDOW = m_focusWindow.lock();\n    m_focusWindow          = pWindow;\n\n    /* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which\n       window focuses are \"via keybinds\" and which ones aren't. */\n    if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace != pWindow->m_workspace && !pWindow->m_pinned && !*PSPECIALFALLTHROUGH)\n        PMONITOR->setSpecialWorkspace(nullptr);\n\n    // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window\n    if (PLASTWINDOW && PLASTWINDOW->m_isMapped) {\n        PLASTWINDOW->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS);\n        PLASTWINDOW->updateDecorationValues();\n\n        if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect())\n            g_pXWaylandManager->activateWindow(PLASTWINDOW, false);\n    }\n\n    const auto PWINDOWSURFACE = surface ? surface : pWindow->wlSurface()->resource();\n\n    rawSurfaceFocus(PWINDOWSURFACE, pWindow);\n\n    g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow\n\n    pWindow->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS);\n    pWindow->onFocusAnimUpdate();\n    pWindow->updateDecorationValues();\n\n    if (pWindow->m_isUrgent)\n        pWindow->m_isUrgent = false;\n\n    // Send an event\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindow\", .data = pWindow->m_class + \",\" + pWindow->m_title});\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindowv2\", .data = std::format(\"{:x}\", rc<uintptr_t>(pWindow.get()))});\n\n    Event::bus()->m_events.window.active.emit(pWindow, reason);\n\n    g_pInputManager->recheckIdleInhibitorStatus();\n\n    if (*PFOLLOWMOUSE == 0)\n        g_pInputManager->sendMotionEventsToFocused();\n\n    if (pWindow->m_group)\n        pWindow->deactivateGroupMembers();\n}\n\nvoid CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWindowOwner) {\n    if (g_pSeatManager->m_state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->m_state.keyboardFocus == pWindowOwner->wlSurface()->resource()))\n        return; // Don't focus when already focused on this.\n\n    if (g_pSessionLockManager->isSessionLocked() && pSurface && !g_pSessionLockManager->isSurfaceSessionLock(pSurface))\n        return;\n\n    if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(pSurface)) {\n        Log::logger->log(Log::DEBUG, \"surface {:x} won't receive kb focus because grab rejected it\", rc<uintptr_t>(pSurface.get()));\n        return;\n    }\n\n    const auto PLASTSURF = m_focusSurface.lock();\n\n    // Unfocus last surface if should\n    if (m_focusSurface && !pWindowOwner)\n        g_pXWaylandManager->activateSurface(m_focusSurface.lock(), false);\n\n    if (!pSurface) {\n        g_pSeatManager->setKeyboardFocus(nullptr);\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindow\", .data = \",\"});\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindowv2\", .data = \"\"});\n        Event::bus()->m_events.input.keyboard.focus.emit(nullptr);\n        m_focusSurface.reset();\n        return;\n    }\n\n    if (g_pSeatManager->m_keyboard)\n        g_pSeatManager->setKeyboardFocus(pSurface);\n\n    if (pWindowOwner)\n        Log::logger->log(Log::DEBUG, \"Set keyboard focus to surface {:x}, with {}\", rc<uintptr_t>(pSurface.get()), pWindowOwner);\n    else\n        Log::logger->log(Log::DEBUG, \"Set keyboard focus to surface {:x}\", rc<uintptr_t>(pSurface.get()));\n\n    g_pXWaylandManager->activateSurface(pSurface, true);\n    m_focusSurface = pSurface;\n\n    Event::bus()->m_events.input.keyboard.focus.emit(pSurface);\n\n    const auto SURF    = Desktop::View::CWLSurface::fromResource(pSurface);\n    const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF);\n\n    if (OLDSURF && OLDSURF->constraint())\n        OLDSURF->constraint()->deactivate();\n\n    if (SURF && SURF->constraint())\n        SURF->constraint()->activate();\n}\n\nvoid CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) {\n    if (m_focusMonitor == pMonitor)\n        return;\n\n    if (!pMonitor) {\n        m_focusMonitor.reset();\n        return;\n    }\n\n    const auto PWORKSPACE = pMonitor->m_activeWorkspace;\n\n    const auto WORKSPACE_ID   = PWORKSPACE ? std::to_string(PWORKSPACE->m_id) : std::to_string(WORKSPACE_INVALID);\n    const auto WORKSPACE_NAME = PWORKSPACE ? PWORKSPACE->m_name : \"?\";\n\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"focusedmon\", .data = pMonitor->m_name + \",\" + WORKSPACE_NAME});\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"focusedmonv2\", .data = pMonitor->m_name + \",\" + WORKSPACE_ID});\n\n    Event::bus()->m_events.monitor.focused.emit(pMonitor);\n    m_focusMonitor = pMonitor;\n}\n\nSP<CWLSurfaceResource> CFocusState::surface() {\n    return m_focusSurface.lock();\n}\n\nPHLWINDOW CFocusState::window() {\n    return m_focusWindow.lock();\n}\n\nPHLMONITOR CFocusState::monitor() {\n    return m_focusMonitor.lock();\n}\n\nvoid CFocusState::resetWindowFocus() {\n    m_focusWindow.reset();\n    m_focusSurface.reset();\n}\n\nbool Desktop::isHardInputFocusReason(eFocusReason r) {\n    return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE;\n}\n"
  },
  {
    "path": "src/desktop/state/FocusState.hpp",
    "content": "#pragma once\n\n#include \"../DesktopTypes.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\n\nnamespace Desktop {\n    enum eFocusReason : uint8_t {\n        FOCUS_REASON_UNKNOWN = 0,\n        FOCUS_REASON_FFM,\n        FOCUS_REASON_KEYBIND,\n        FOCUS_REASON_CLICK,\n        FOCUS_REASON_OTHER,\n        FOCUS_REASON_DESKTOP_STATE_CHANGE,\n        FOCUS_REASON_NEW_WINDOW,\n        FOCUS_REASON_GHOSTS,\n    };\n\n    bool isHardInputFocusReason(eFocusReason r);\n\n    class CFocusState {\n      public:\n        CFocusState();\n        ~CFocusState() = default;\n\n        CFocusState(CFocusState&&)      = delete;\n        CFocusState(CFocusState&)       = delete;\n        CFocusState(const CFocusState&) = delete;\n\n        void                   fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);\n        void                   rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr);\n        void                   rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr);\n        void                   rawMonitorFocus(PHLMONITOR m);\n\n        void                   resetWindowFocus();\n\n        SP<CWLSurfaceResource> surface();\n        PHLWINDOW              window();\n        PHLMONITOR             monitor();\n\n      private:\n        WP<CWLSurfaceResource> m_focusSurface;\n        PHLWINDOWREF           m_focusWindow;\n        PHLMONITORREF          m_focusMonitor;\n\n        CHyprSignalListener    m_windowOpen, m_windowClose;\n    };\n\n    SP<CFocusState> focusState();\n};\n"
  },
  {
    "path": "src/desktop/types/OverridableVar.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <type_traits>\n#include <any>\n#include <ranges>\n#include <algorithm>\n#include \"../../config/ConfigValue.hpp\"\n\nnamespace Desktop::Types {\n\n    struct SAlphaValue {\n        float alpha      = 1.F;\n        bool  overridden = false;\n\n        float applyAlpha(float a) const {\n            if (overridden)\n                return alpha;\n            else\n                return alpha * a;\n        };\n    };\n\n    enum eOverridePriority : uint8_t {\n        PRIORITY_LAYOUT = 0,\n        PRIORITY_WORKSPACE_RULE,\n        PRIORITY_WINDOW_RULE,\n        PRIORITY_SET_PROP,\n\n        PRIORITY_END,\n    };\n\n    template <typename T>\n    T clampOptional(T const& value, std::optional<T> const& min, std::optional<T> const& max) {\n        return std::clamp(value, min.value_or(std::numeric_limits<T>::min()), max.value_or(std::numeric_limits<T>::max()));\n    }\n\n    template <typename T, bool Extended = std::is_same_v<T, bool> || std::is_same_v<T, Hyprlang::INT> || std::is_same_v<T, Hyprlang::FLOAT>>\n    class COverridableVar {\n      public:\n        COverridableVar(T const& value, eOverridePriority priority) {\n            m_values[priority] = value;\n        }\n\n        COverridableVar(T const& value) : m_defaultValue{value} {}\n        COverridableVar(T const& value, std::optional<T> const& min, std::optional<T> const& max = std::nullopt) : m_defaultValue{value}, m_minValue{min}, m_maxValue{max} {}\n        COverridableVar(std::string const& value)\n            requires(Extended && !std::is_same_v<T, bool>)\n            : m_configValue(SP<CConfigValue<T>>(new CConfigValue<T>(value))) {}\n        COverridableVar(std::string const& value, std::optional<T> const& min, std::optional<T> const& max = std::nullopt)\n            requires(Extended && !std::is_same_v<T, bool>)\n            : m_minValue(min), m_maxValue(max), m_configValue(SP<CConfigValue<T>>(new CConfigValue<T>(value))) {}\n\n        COverridableVar()  = default;\n        ~COverridableVar() = default;\n\n        COverridableVar& operator=(COverridableVar<T> const& other) {\n            // Self-assignment check\n            if (this == &other)\n                return *this;\n\n            for (size_t i = 0; i < PRIORITY_END; ++i) {\n                if constexpr (Extended && !std::is_same_v<T, bool>)\n                    m_values[i] = other.m_values[i].has_value() ? clampOptional(*other.m_values[i], m_minValue, m_maxValue) : other.m_values[i];\n                else\n                    m_values[i] = other.m_values[i];\n            }\n\n            return *this;\n        }\n\n        void set(T value, eOverridePriority priority) {\n            m_values[priority] = value;\n        }\n\n        void unset(eOverridePriority priority) {\n            m_values[priority] = std::nullopt;\n        }\n\n        bool hasValue() const {\n            return std::ranges::any_of(m_values, [](const auto& e) { return e.has_value(); });\n        }\n\n        T value() const {\n            for (const auto& v : m_values | std::ranges::views::reverse) {\n                if (v)\n                    return *v;\n            }\n            throw std::bad_optional_access();\n        }\n\n        T valueOr(T const& other) const {\n            if (hasValue())\n                return value();\n            else\n                return other;\n        }\n\n        T valueOrDefault() const\n            requires(Extended && !std::is_same_v<T, bool>)\n        {\n            if (hasValue())\n                return value();\n            else if (m_defaultValue.has_value())\n                return m_defaultValue.value();\n            else\n                return **std::any_cast<SP<CConfigValue<T>>>(m_configValue);\n        }\n\n        T valueOrDefault() const\n            requires(!Extended || std::is_same_v<T, bool>)\n        {\n            if (hasValue())\n                return value();\n            else if (!m_defaultValue.has_value())\n                throw std::bad_optional_access();\n            else\n                return m_defaultValue.value();\n        }\n\n        eOverridePriority getPriority() const {\n            for (int i = PRIORITY_END - 1; i >= 0; --i) {\n                if (m_values[i])\n                    return sc<eOverridePriority>(i);\n            }\n\n            throw std::bad_optional_access();\n        }\n\n        void increment(T const& other, eOverridePriority priority) {\n            if constexpr (std::is_same_v<T, bool>)\n                m_values[priority] = valueOr(false) ^ other;\n            else\n                m_values[priority] = clampOptional(valueOrDefault() + other, m_minValue, m_maxValue);\n        }\n\n        void matchOptional(std::optional<T> const& optValue, eOverridePriority priority) {\n            if (optValue.has_value())\n                m_values[priority] = optValue.value();\n            else\n                unset(priority);\n        }\n\n        operator std::optional<T>() {\n            if (hasValue())\n                return value();\n            else\n                return std::nullopt;\n        }\n\n      private:\n        std::array<std::optional<T>, PRIORITY_END> m_values;\n        std::optional<T>                           m_defaultValue; // used for toggling, so required for bool\n        std::optional<T>                           m_minValue;\n        std::optional<T>                           m_maxValue;\n        std::any                                   m_configValue; // only there for select variables\n    };\n\n}"
  },
  {
    "path": "src/desktop/view/GlobalViewMethods.cpp",
    "content": "#include \"GlobalViewMethods.hpp\"\n#include \"../../Compositor.hpp\"\n\n#include \"LayerSurface.hpp\"\n#include \"Window.hpp\"\n#include \"Popup.hpp\"\n#include \"Subsurface.hpp\"\n#include \"SessionLock.hpp\"\n\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../protocols/core/Subcompositor.hpp\"\n#include \"../../protocols/SessionLock.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nstd::vector<SP<IView>> View::getViewsForWorkspace(PHLWORKSPACE ws) {\n    std::vector<SP<IView>> views;\n\n    for (const auto& w : g_pCompositor->m_windows) {\n        if (!w->aliveAndVisible() || w->m_workspace != ws)\n            continue;\n\n        views.emplace_back(w);\n\n        w->wlSurface()->resource()->breadthfirst(\n            [&views](SP<CWLSurfaceResource> s, const Vector2D& pos, void* data) {\n                auto surf = CWLSurface::fromResource(s);\n                if (!surf || !s->m_mapped)\n                    return;\n\n                views.emplace_back(surf->view());\n            },\n            nullptr);\n\n        // xwl windows dont have this\n        if (w->m_popupHead) {\n            w->m_popupHead->breadthfirst(\n                [&views](SP<CPopup> s, void* data) {\n                    auto surf = s->wlSurface();\n                    if (!surf || !s->aliveAndVisible())\n                        return;\n\n                    views.emplace_back(surf->view());\n                },\n                nullptr);\n        }\n    }\n\n    for (const auto& l : g_pCompositor->m_layers) {\n        if (!l->aliveAndVisible() || l->m_monitor != ws->m_monitor)\n            continue;\n\n        views.emplace_back(l);\n\n        l->m_popupHead->breadthfirst(\n            [&views](SP<CPopup> p, void* data) {\n                auto surf = p->wlSurface();\n                if (!surf || !p->aliveAndVisible())\n                    return;\n\n                views.emplace_back(surf->view());\n            },\n            nullptr);\n    }\n\n    for (const auto& v : g_pCompositor->m_otherViews) {\n        if (!v->aliveAndVisible() || !v->desktopComponent())\n            continue;\n\n        if (v->type() == VIEW_TYPE_LOCK_SCREEN) {\n            const auto LOCK = Desktop::View::CSessionLock::fromView(v);\n            if (LOCK->monitor() != ws->m_monitor)\n                continue;\n\n            views.emplace_back(LOCK);\n            continue;\n        }\n    }\n\n    return views;\n}\n"
  },
  {
    "path": "src/desktop/view/GlobalViewMethods.hpp",
    "content": "#pragma once\n\n#include \"View.hpp\"\n\n#include \"../Workspace.hpp\"\n\n#include <vector>\n\nnamespace Desktop::View {\n    std::vector<SP<IView>> getViewsForWorkspace(PHLWORKSPACE ws);\n};"
  },
  {
    "path": "src/desktop/view/Group.cpp",
    "content": "#include \"Group.hpp\"\n#include \"Window.hpp\"\n\n#include \"../../render/decorations/CHyprGroupBarDecoration.hpp\"\n#include \"../../layout/target/WindowGroupTarget.hpp\"\n#include \"../../layout/target/WindowTarget.hpp\"\n#include \"../../layout/target/Target.hpp\"\n#include \"../../layout/space/Space.hpp\"\n#include \"../../layout/LayoutManager.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../Compositor.hpp\"\n\n#include <algorithm>\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nstd::vector<WP<CGroup>>& View::groups() {\n    static std::vector<WP<CGroup>> g;\n    return g;\n}\n\nSP<CGroup> CGroup::create(std::vector<PHLWINDOWREF>&& windows) {\n    auto x      = SP<CGroup>(new CGroup(std::move(windows)));\n    x->m_self   = x;\n    x->m_target = Layout::CWindowGroupTarget::create(x);\n    groups().emplace_back(x);\n\n    x->init();\n\n    return x;\n}\n\nCGroup::CGroup(std::vector<PHLWINDOWREF>&& windows) : m_windows(std::move(windows)) {\n    ;\n}\n\nvoid CGroup::init() {\n    // for proper group logic:\n    //  - add all windows to us\n    //  - replace the first window with our target\n    //  - remove all window targets from layout\n    //  - apply updates\n\n    // FIXME: what if some windows are grouped? For now we only do 1-window but YNK\n    for (const auto& w : m_windows) {\n        RASSERT(!w->m_group, \"CGroup: windows cannot contain grouped in init, this will explode\");\n        w->m_group = m_self.lock();\n        m_groupPolicyFlags |= w->m_groupRules;\n    }\n\n    g_layoutManager->switchTargets(m_windows.at(0)->m_target, m_target);\n\n    for (const auto& w : m_windows) {\n        w->m_target->setSpaceGhost(m_target->space());\n    }\n\n    for (const auto& w : m_windows) {\n        applyWindowDecosAndUpdates(w.lock());\n    }\n\n    updateWindowVisibility();\n}\n\nvoid CGroup::destroy() {\n    while (true) {\n        if (m_windows.size() == 1) {\n            remove(m_windows.at(0).lock());\n            break;\n        }\n\n        remove(m_windows.at(0).lock());\n    }\n}\n\nCGroup::~CGroup() {\n    if (m_target->space())\n        m_target->assignToSpace(nullptr);\n    std::erase_if(groups(), [this](const auto& e) { return !e || e == m_self; });\n}\n\nbool CGroup::has(PHLWINDOW w) const {\n    return std::ranges::contains(m_windows, w);\n}\n\nvoid CGroup::add(PHLWINDOW w) {\n    static auto INSERT_AFTER_CURRENT = CConfigValue<Hyprlang::INT>(\"group:insert_after_current\");\n\n    if (w->m_group) {\n        if (w->m_group == m_self)\n            return;\n\n        const auto WINDOWS = w->m_group->windows();\n        for (const auto& w : WINDOWS) {\n            w->m_group->remove(w.lock());\n            add(w.lock());\n        }\n\n        return;\n    }\n\n    if (w->layoutTarget()->space()) {\n        // remove the target from a space if it is in one\n        g_layoutManager->removeTarget(w->layoutTarget());\n    }\n\n    w->m_group = m_self.lock();\n    m_groupPolicyFlags |= w->m_groupRules;\n    w->m_target->setSpaceGhost(m_target->space());\n    w->m_target->setFloating(m_target->floating());\n\n    if (*INSERT_AFTER_CURRENT) {\n        m_windows.insert(m_windows.begin() + m_current + 1, w);\n        m_current++;\n    } else {\n        m_windows.emplace_back(w);\n        m_current = m_windows.size() - 1;\n    }\n\n    applyWindowDecosAndUpdates(w);\n    updateWindowVisibility();\n    m_target->recalc();\n}\n\nvoid CGroup::remove(PHLWINDOW w, Math::eDirection dir) {\n    std::optional<size_t> idx;\n    for (size_t i = 0; i < m_windows.size(); ++i) {\n        if (m_windows.at(i) == w) {\n            idx = i;\n            break;\n        }\n    }\n\n    if (!idx)\n        return;\n\n    if ((m_current >= *idx && idx != 0) || (m_current >= m_windows.size() - 1 && m_current > 0))\n        m_current--;\n\n    auto g = m_self.lock(); // keep ref to avoid uaf after w->m_group.reset()\n\n    w->m_group.reset();\n    removeWindowDecos(w);\n\n    w->setHidden(false);\n\n    const bool REMOVING_GROUP = m_windows.size() <= 1;\n\n    if (REMOVING_GROUP) {\n        w->m_target->assignToSpace(nullptr);\n        g_layoutManager->switchTargets(m_target, w->m_target);\n    }\n\n    // we do it after the above because switchTargets expects this to be a valid group\n    m_windows.erase(m_windows.begin() + *idx);\n\n    if (!m_windows.empty())\n        updateWindowVisibility();\n\n    // do this here: otherwise the new current is hidden and workspace rules get wrong data\n    if (!REMOVING_GROUP) {\n        std::optional<Vector2D> focalPoint;\n        if (dir != Math::DIRECTION_DEFAULT) {\n            const auto box = m_target->position();\n            switch (dir) {\n                case Math::DIRECTION_RIGHT: focalPoint = Vector2D(box.x + box.w, box.y + box.h / 2.0); break;\n                case Math::DIRECTION_LEFT: focalPoint = Vector2D(box.x, box.y + box.h / 2.0); break;\n                case Math::DIRECTION_DOWN: focalPoint = Vector2D(box.x + box.w / 2.0, box.y + box.h); break;\n                case Math::DIRECTION_UP: focalPoint = Vector2D(box.x + box.w / 2.0, box.y); break;\n                default: break;\n            }\n        }\n        w->m_target->assignToSpace(m_target->space(), focalPoint);\n    }\n}\n\nvoid CGroup::moveCurrent(bool next) {\n    size_t idx = m_current;\n\n    if (next) {\n        idx++;\n        if (idx >= m_windows.size())\n            idx = 0;\n    } else {\n        if (idx == 0)\n            idx = m_windows.size() - 1;\n        else\n            idx--;\n    }\n\n    setCurrent(idx);\n}\n\nvoid CGroup::setCurrent(size_t idx) {\n    if (idx == m_current)\n        return;\n\n    const auto FS_STATE  = m_target->fullscreenMode();\n    const auto WASFOCUS  = Desktop::focusState()->window() == current();\n    auto       oldWindow = m_windows.at(m_current).lock();\n\n    if (FS_STATE != FSMODE_NONE)\n        g_pCompositor->setWindowFullscreenInternal(oldWindow, FSMODE_NONE);\n\n    m_current = std::clamp(idx, sc<size_t>(0), m_windows.size() - 1);\n    updateWindowVisibility();\n\n    auto newWindow = m_windows.at(m_current).lock();\n\n    if (FS_STATE != FSMODE_NONE) {\n        g_pCompositor->setWindowFullscreenInternal(newWindow, FS_STATE);\n        newWindow->m_target->warpPositionSize();\n        oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks\n    }\n\n    if (WASFOCUS)\n        Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE);\n}\n\nvoid CGroup::setCurrent(PHLWINDOW w) {\n    if (w == current())\n        return;\n\n    for (size_t i = 0; i < m_windows.size(); ++i) {\n        if (m_windows.at(i) == w) {\n            setCurrent(i);\n            return;\n        }\n    }\n}\n\nsize_t CGroup::getCurrentIdx() const {\n    return m_current;\n}\n\nPHLWINDOW CGroup::head() const {\n    return m_windows.front().lock();\n}\n\nPHLWINDOW CGroup::tail() const {\n    return m_windows.back().lock();\n}\n\nPHLWINDOW CGroup::current() const {\n    return m_windows.at(m_current).lock();\n}\n\nPHLWINDOW CGroup::next() const {\n    return (m_current >= m_windows.size() - 1 ? m_windows.front() : m_windows.at(m_current + 1)).lock();\n}\n\nPHLWINDOW CGroup::fromIndex(size_t idx) const {\n    if (idx >= m_windows.size())\n        return nullptr;\n\n    return m_windows.at(idx).lock();\n}\n\nconst std::vector<PHLWINDOWREF>& CGroup::windows() const {\n    return m_windows;\n}\n\nvoid CGroup::applyWindowDecosAndUpdates(PHLWINDOW x) {\n    x->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(x));\n\n    x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n    x->updateWindowDecos();\n    x->updateDecorationValues();\n}\n\nvoid CGroup::removeWindowDecos(PHLWINDOW x) {\n    x->removeWindowDeco(x->getDecorationByType(DECORATION_GROUPBAR));\n\n    x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n    x->updateWindowDecos();\n    x->updateDecorationValues();\n}\n\nvoid CGroup::updateWindowVisibility() {\n    for (size_t i = 0; i < m_windows.size(); ++i) {\n        if (i == m_current) {\n            auto& x = m_windows.at(i);\n            x->setHidden(false);\n            x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);\n            x->updateWindowDecos();\n            x->updateDecorationValues();\n        } else\n            m_windows.at(i)->setHidden(true);\n    }\n\n    m_target->recalc();\n\n    m_target->damageEntire();\n}\n\nsize_t CGroup::size() const {\n    return m_windows.size();\n}\n\nbool CGroup::locked() const {\n    return m_groupPolicyFlags & GROUP_LOCK;\n}\n\nvoid CGroup::setLocked(bool x) {\n    if (x)\n        m_groupPolicyFlags |= GROUP_LOCK;\n    else\n        m_groupPolicyFlags &= ~GROUP_LOCK;\n}\n\nbool CGroup::denied() const {\n    return m_groupPolicyFlags & GROUP_DENY;\n}\n\nvoid CGroup::setDenied(bool x) {\n    if (x)\n        m_groupPolicyFlags |= GROUP_DENY;\n    else\n        m_groupPolicyFlags &= ~GROUP_DENY;\n}\n\nvoid CGroup::updateWorkspace(PHLWORKSPACE ws) {\n    if (!ws)\n        return;\n\n    for (const auto& w : windows()) {\n        w->m_monitor = ws->m_monitor;\n        w->moveToWorkspace(ws);\n        w->updateToplevel();\n        w->updateWindowDecos();\n        w->m_target->setSpaceGhost(ws->m_space);\n    }\n}\n\nvoid CGroup::swapWithNext() {\n    const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);\n\n    size_t     idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1;\n    std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);\n    m_current = idx;\n\n    updateWindowVisibility();\n\n    if (HAD_FOCUS)\n        Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);\n}\n\nvoid CGroup::swapWithLast() {\n    const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);\n\n    size_t     idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1;\n    std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);\n    m_current = idx;\n\n    updateWindowVisibility();\n\n    if (HAD_FOCUS)\n        Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);\n}\n"
  },
  {
    "path": "src/desktop/view/Group.hpp",
    "content": "#pragma once\n\n#include \"../DesktopTypes.hpp\"\n#include \"../../helpers/math/Direction.hpp\"\n\n#include <vector>\n\nnamespace Layout {\n    class CWindowGroupTarget;\n};\n\nnamespace Desktop::View {\n    class CGroup {\n      public:\n        static SP<CGroup> create(std::vector<PHLWINDOWREF>&& windows);\n        ~CGroup();\n\n        bool                             has(PHLWINDOW w) const;\n\n        void                             add(PHLWINDOW w);\n        void                             remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT);\n        void                             moveCurrent(bool next);\n        void                             setCurrent(size_t idx);\n        void                             setCurrent(PHLWINDOW w);\n        size_t                           getCurrentIdx() const;\n        size_t                           size() const;\n        void                             destroy();\n        void                             updateWorkspace(PHLWORKSPACE);\n\n        void                             swapWithNext();\n        void                             swapWithLast();\n\n        PHLWINDOW                        head() const;\n        PHLWINDOW                        tail() const;\n        PHLWINDOW                        current() const;\n        PHLWINDOW                        next() const;\n\n        PHLWINDOW                        fromIndex(size_t idx) const;\n\n        bool                             locked() const;\n        void                             setLocked(bool x);\n\n        bool                             denied() const;\n        void                             setDenied(bool x);\n\n        const std::vector<PHLWINDOWREF>& windows() const;\n\n        SP<Layout::CWindowGroupTarget>   m_target;\n\n      private:\n        CGroup(std::vector<PHLWINDOWREF>&& windows);\n\n        void                      applyWindowDecosAndUpdates(PHLWINDOW x);\n        void                      removeWindowDecos(PHLWINDOW x);\n        void                      init();\n        void                      updateWindowVisibility();\n\n        WP<CGroup>                m_self;\n\n        std::vector<PHLWINDOWREF> m_windows;\n\n        size_t                    m_current = 0;\n\n        uint32_t                  m_groupPolicyFlags = 0;\n    };\n\n    std::vector<WP<CGroup>>& groups();\n};\n"
  },
  {
    "path": "src/desktop/view/LayerSurface.cpp",
    "content": "#include \"LayerSurface.hpp\"\n#include \"../state/FocusState.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../protocols/LayerShell.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../managers/animation/AnimationManager.hpp\"\n#include \"../../managers/animation/DesktopAnimationManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../managers/EventManager.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nPHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {\n    PHLLS pLS = SP<CLayerSurface>(new CLayerSurface(resource));\n\n    auto  pMonitor = resource->m_monitor.empty() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromName(resource->m_monitor);\n\n    pLS->m_wlSurface->assign(resource->m_surface.lock(), pLS);\n\n    pLS->m_ruleApplicator = makeUnique<Desktop::Rule::CLayerRuleApplicator>(pLS);\n    pLS->m_self           = pLS;\n    pLS->m_namespace      = resource->m_layerNamespace;\n    pLS->m_layer          = std::clamp(resource->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);\n    pLS->m_popupHead      = CPopup::create(pLS);\n\n    g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeLayersIn\"), pLS, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig(\"layersIn\"), pLS, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realSize, g_pConfigManager->getAnimationPropertyConfig(\"layersIn\"), pLS, AVARDAMAGE_ENTIRE);\n\n    pLS->registerCallbacks();\n\n    pLS->m_alpha->setValueAndWarp(0.f);\n\n    if (!pMonitor) {\n        Log::logger->log(Log::DEBUG, \"LayerSurface {:x} (namespace {} layer {}) created on NO MONITOR ?!\", rc<uintptr_t>(resource.get()), resource->m_layerNamespace,\n                         sc<int>(pLS->m_layer));\n\n        return pLS;\n    }\n\n    if (pMonitor->m_mirrorOf)\n        pMonitor = g_pCompositor->m_monitors.front();\n\n    pLS->m_monitor = pMonitor;\n    pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS);\n\n    Log::logger->log(Log::DEBUG, \"LayerSurface {:x} (namespace {} layer {}) created on monitor {}\", rc<uintptr_t>(resource.get()), resource->m_layerNamespace,\n                     sc<int>(pLS->m_layer), pMonitor->m_name);\n\n    return pLS;\n}\n\nPHLLS CLayerSurface::fromView(SP<IView> v) {\n    if (!v || v->type() != VIEW_TYPE_LAYER_SURFACE)\n        return nullptr;\n    return dynamicPointerCast<CLayerSurface>(v);\n}\n\nvoid CLayerSurface::registerCallbacks() {\n    m_alpha->setUpdateCallback([this](auto) {\n        if (m_ruleApplicator->dimAround().valueOrDefault() && m_monitor)\n            g_pHyprRenderer->damageMonitor(m_monitor.lock());\n    });\n}\n\nCLayerSurface::CLayerSurface(SP<CLayerShellResource> resource_) : IView(CWLSurface::create()), m_layerSurface(resource_) {\n    m_listeners.commit  = m_layerSurface->m_events.commit.listen([this] { onCommit(); });\n    m_listeners.map     = m_layerSurface->m_events.map.listen([this] { onMap(); });\n    m_listeners.unmap   = m_layerSurface->m_events.unmap.listen([this] { onUnmap(); });\n    m_listeners.destroy = m_layerSurface->m_events.destroy.listen([this] { onDestroy(); });\n}\n\nCLayerSurface::~CLayerSurface() {\n    if (m_wlSurface)\n        m_wlSurface->unassign();\n\n    for (auto const& mon : g_pCompositor->m_realMonitors) {\n        for (auto& lsl : mon->m_layerSurfaceLayers) {\n            std::erase_if(lsl, [this](auto& ls) { return ls.expired() || ls.get() == this; });\n        }\n    }\n}\n\neViewType CLayerSurface::type() const {\n    return VIEW_TYPE_LAYER_SURFACE;\n}\n\nbool CLayerSurface::visible() const {\n    return (m_mapped && m_layerSurface && m_layerSurface->m_mapped && m_wlSurface && m_wlSurface->resource()) || (m_fadingOut && m_alpha->value() > 0.F);\n}\n\nstd::optional<CBox> CLayerSurface::logicalBox() const {\n    return surfaceLogicalBox();\n}\n\nstd::optional<CBox> CLayerSurface::surfaceLogicalBox() const {\n    if (!visible())\n        return std::nullopt;\n\n    return CBox{m_realPosition->value(), m_realSize->value()};\n}\n\nbool CLayerSurface::desktopComponent() const {\n    return true;\n}\n\nvoid CLayerSurface::onDestroy() {\n    Log::logger->log(Log::DEBUG, \"LayerSurface {:x} destroyed\", rc<uintptr_t>(m_layerSurface.get()));\n\n    const auto PMONITOR = m_monitor.lock();\n\n    if (!PMONITOR)\n        Log::logger->log(Log::WARN, \"Layersurface destroyed on an invalid monitor (removed?)\");\n\n    if (!m_fadingOut) {\n        if (m_mapped) {\n            Log::logger->log(Log::DEBUG, \"Forcing an unmap of a LS that did a straight destroy!\");\n            onUnmap();\n        } else {\n            Log::logger->log(Log::DEBUG, \"Removing LayerSurface that wasn't mapped.\");\n            if (m_alpha)\n                g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n            m_fadingOut = true;\n            g_pCompositor->addToFadingOutSafe(m_self.lock());\n        }\n    }\n\n    m_popupHead.reset();\n\n    m_noProcess = true;\n\n    // rearrange to fix the reserved areas\n    if (PMONITOR) {\n        g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id);\n        PMONITOR->m_scheduledRecalc = true;\n\n        // and damage\n        CBox geomFixed = {m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y, m_geometry.width, m_geometry.height};\n        g_pHyprRenderer->damageBox(geomFixed);\n    }\n\n    m_readyToDelete = true;\n    m_layerSurface.reset();\n    if (m_wlSurface)\n        m_wlSurface->unassign();\n\n    m_listeners.unmap.reset();\n    m_listeners.destroy.reset();\n    m_listeners.map.reset();\n    m_listeners.commit.reset();\n}\n\nvoid CLayerSurface::onMap() {\n    Log::logger->log(Log::DEBUG, \"LayerSurface {:x} mapped\", rc<uintptr_t>(m_layerSurface.get()));\n\n    m_mapped          = true;\n    m_interactivity   = m_layerSurface->m_current.interactivity;\n    m_aboveFullscreen = true;\n\n    m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL);\n\n    m_layerSurface->m_surface->map();\n\n    // this layer might be re-mapped.\n    m_fadingOut = false;\n    g_pCompositor->removeFromFadingOutSafe(m_self.lock());\n\n    // fix if it changed its mon\n    const auto PMONITOR = m_monitor.lock();\n\n    if (!PMONITOR)\n        return;\n\n    PMONITOR->m_scheduledRecalc = true;\n\n    g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id);\n\n    m_wlSurface->resource()->enter(PMONITOR->m_self.lock());\n\n    const bool ISEXCLUSIVE = m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE;\n\n    if (ISEXCLUSIVE)\n        g_pInputManager->m_exclusiveLSes.push_back(m_self);\n\n    const bool GRABSFOCUS = ISEXCLUSIVE ||\n        (m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE &&\n         // don't focus if constrained\n         (g_pSeatManager->m_mouse.expired() || !g_pInputManager->isConstrained()));\n\n    if (GRABSFOCUS) {\n        // TODO: use the new superb really very cool grab\n        if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(m_wlSurface->resource()))\n            g_pSeatManager->setGrab(nullptr);\n\n        g_pInputManager->releaseAllMouseButtons();\n        Desktop::focusState()->rawSurfaceFocus(m_wlSurface->resource());\n\n        const auto LOCAL = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y);\n        g_pSeatManager->setPointerFocus(m_wlSurface->resource(), LOCAL);\n        g_pInputManager->m_emptyFocusCursorSet = false;\n    }\n\n    m_position = Vector2D(m_geometry.x, m_geometry.y);\n\n    CBox geomFixed = {m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y, m_geometry.width, m_geometry.height};\n    g_pHyprRenderer->damageBox(geomFixed);\n\n    g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN);\n\n    m_readyToDelete = false;\n    m_fadingOut     = false;\n\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"openlayer\", .data = m_namespace});\n    Event::bus()->m_events.layer.opened.emit(m_self.lock());\n\n    g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale);\n    g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform);\n}\n\nvoid CLayerSurface::onUnmap() {\n    Log::logger->log(Log::DEBUG, \"LayerSurface {:x} unmapped\", rc<uintptr_t>(m_layerSurface.get()));\n\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"closelayer\", .data = m_layerSurface->m_layerNamespace});\n    Event::bus()->m_events.layer.closed.emit(m_self.lock());\n\n    std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; });\n\n    if (!m_monitor || g_pCompositor->m_unsafeState) {\n        Log::logger->log(Log::WARN, \"Layersurface unmapping on invalid monitor (removed?) ignoring.\");\n\n        g_pCompositor->addToFadingOutSafe(m_self.lock());\n\n        m_mapped = false;\n        if (m_layerSurface && m_layerSurface->m_surface)\n            m_layerSurface->m_surface->unmap();\n\n        g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n        return;\n    }\n\n    // end any pending animations so that snapshot has right dimensions\n    m_realPosition->warp();\n    m_realSize->warp();\n\n    // make a snapshot and start fade\n    g_pHyprRenderer->makeSnapshot(m_self.lock());\n\n    g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n    m_fadingOut = true;\n\n    m_mapped = false;\n    if (m_layerSurface && m_layerSurface->m_surface)\n        m_layerSurface->m_surface->unmap();\n\n    g_pCompositor->addToFadingOutSafe(m_self.lock());\n\n    const auto PMONITOR = m_monitor.lock();\n\n    const bool WASLASTFOCUS = g_pSeatManager->m_state.keyboardFocus == m_wlSurface->resource() || g_pSeatManager->m_state.pointerFocus == m_wlSurface->resource();\n\n    if (!PMONITOR)\n        return;\n\n    // refocus if needed\n    //                                vvvvvvvvvvvvv if there is a last focus and the last focus is not keyboard focusable, fallback to window\n    if (WASLASTFOCUS ||\n        (Desktop::focusState()->surface() && Desktop::focusState()->surface()->m_hlSurface && !Desktop::focusState()->surface()->m_hlSurface->keyboardFocusable())) {\n        if (!g_pInputManager->refocusLastWindow(PMONITOR))\n            g_pInputManager->refocus();\n    } else if (Desktop::focusState()->surface() && Desktop::focusState()->surface() != m_wlSurface->resource())\n        g_pSeatManager->setKeyboardFocus(Desktop::focusState()->surface());\n\n    CBox geomFixed = {m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y, m_geometry.width, m_geometry.height};\n    g_pHyprRenderer->damageBox(geomFixed);\n\n    geomFixed = {m_geometry.x + sc<int>(PMONITOR->m_position.x), m_geometry.y + sc<int>(PMONITOR->m_position.y), sc<int>(m_layerSurface->m_surface->m_current.size.x),\n                 sc<int>(m_layerSurface->m_surface->m_current.size.y)};\n    g_pHyprRenderer->damageBox(geomFixed);\n\n    g_pInputManager->simulateMouseMovement();\n\n    g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id);\n}\n\nvoid CLayerSurface::onCommit() {\n    if (!m_layerSurface)\n        return;\n\n    if (!m_mapped) {\n        // we're re-mapping if this is the case\n        if (m_layerSurface->m_surface && !m_layerSurface->m_surface->m_current.texture) {\n            m_fadingOut = false;\n            m_geometry  = {};\n            g_pHyprRenderer->arrangeLayersForMonitor(monitorID());\n        }\n\n        return;\n    }\n\n    const auto PMONITOR = m_monitor.lock();\n\n    if (!PMONITOR)\n        return;\n\n    if (m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM)\n        PMONITOR->m_blurFBDirty = true;\n\n    CBox geomFixed = {m_geometry.x, m_geometry.y, m_geometry.width, m_geometry.height};\n    g_pHyprRenderer->damageBox(geomFixed);\n\n    if (m_layerSurface->m_current.committed != 0) {\n        if (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_LAYER && m_layerSurface->m_current.layer != m_layer) {\n\n            const auto NEW_LAYER = std::clamp(m_layerSurface->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);\n\n            for (auto it = PMONITOR->m_layerSurfaceLayers[m_layer].begin(); it != PMONITOR->m_layerSurfaceLayers[m_layer].end(); it++) {\n                if (*it == m_self) {\n                    PMONITOR->m_layerSurfaceLayers[NEW_LAYER].emplace_back(*it);\n                    PMONITOR->m_layerSurfaceLayers[m_layer].erase(it);\n                    break;\n                }\n            }\n\n            m_layer           = NEW_LAYER;\n            m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;\n\n            // if in fullscreen, only overlay can be above.\n            *m_alpha = PMONITOR->inFullscreenMode() ? (m_layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ? 1.F : 0.F) : 1.F;\n\n            if (m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM)\n                PMONITOR->m_blurFBDirty = true; // so that blur is recalc'd\n        }\n\n        g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id);\n\n        PMONITOR->m_scheduledRecalc = true;\n    } else {\n        m_position = Vector2D(m_geometry.x, m_geometry.y);\n\n        // update geom if it changed\n        if (m_layerSurface->m_surface->m_current.scale == 1 && PMONITOR->m_scale != 1.f && m_layerSurface->m_surface->m_current.viewport.hasDestination) {\n            // fractional scaling. Dirty hack.\n            m_geometry = {m_geometry.pos(), m_layerSurface->m_surface->m_current.viewport.destination};\n        } else {\n            // this is because some apps like e.g. rofi-lbonn can't fucking use the protocol correctly.\n            m_geometry = {m_geometry.pos(), m_layerSurface->m_surface->m_current.size};\n        }\n    }\n\n    if (m_realPosition->goal() != m_geometry.pos()) {\n        if (m_realPosition->isBeingAnimated())\n            *m_realPosition = m_geometry.pos();\n        else\n            m_realPosition->setValueAndWarp(m_geometry.pos());\n    }\n    if (m_realSize->goal() != m_geometry.size()) {\n        if (m_realSize->isBeingAnimated())\n            *m_realSize = m_geometry.size();\n        else\n            m_realSize->setValueAndWarp(m_geometry.size());\n    }\n\n    if (m_mapped && (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_INTERACTIVITY)) {\n        bool WASLASTFOCUS = false;\n        m_layerSurface->m_surface->breadthfirst(\n            [&WASLASTFOCUS](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) { WASLASTFOCUS = WASLASTFOCUS || g_pSeatManager->m_state.keyboardFocus == surf; },\n            nullptr);\n        if (!WASLASTFOCUS && m_popupHead) {\n            m_popupHead->breadthfirst(\n                [&WASLASTFOCUS](WP<Desktop::View::CPopup> popup, void* data) {\n                    WASLASTFOCUS = WASLASTFOCUS || (popup->wlSurface() && g_pSeatManager->m_state.keyboardFocus == popup->wlSurface()->resource());\n                },\n                nullptr);\n        }\n        const bool WASEXCLUSIVE = m_interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE;\n        const bool ISEXCLUSIVE  = m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE;\n\n        if (!WASEXCLUSIVE && ISEXCLUSIVE)\n            g_pInputManager->m_exclusiveLSes.push_back(m_self);\n        else if (WASEXCLUSIVE && !ISEXCLUSIVE)\n            std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other.lock() || other.lock() == m_self.lock(); });\n\n        // if the surface was focused and interactive but now isn't, refocus\n        if (WASLASTFOCUS && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) {\n            // moveMouseUnified won't focus non interactive layers but it won't unfocus them either,\n            // so unfocus the surface here.\n            Desktop::focusState()->rawSurfaceFocus(nullptr);\n            g_pInputManager->refocusLastWindow(m_monitor.lock());\n        } else if (WASLASTFOCUS && WASEXCLUSIVE && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) {\n            g_pInputManager->simulateMouseMovement();\n        } else if (!WASEXCLUSIVE && ISEXCLUSIVE) {\n            // if now exclusive and not previously\n            g_pSeatManager->setGrab(nullptr);\n            g_pInputManager->releaseAllMouseButtons();\n            Desktop::focusState()->rawSurfaceFocus(m_wlSurface->resource());\n\n            const auto LOCAL = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y);\n            g_pSeatManager->setPointerFocus(m_wlSurface->resource(), LOCAL);\n            g_pInputManager->m_emptyFocusCursorSet = false;\n        }\n    }\n\n    m_interactivity = m_layerSurface->m_current.interactivity;\n\n    g_pHyprRenderer->damageSurface(m_wlSurface->resource(), m_position.x, m_position.y);\n\n    g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale);\n    g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform);\n}\n\nbool CLayerSurface::isFadedOut() {\n    if (!m_fadingOut)\n        return false;\n\n    return !m_realPosition->isBeingAnimated() && !m_realSize->isBeingAnimated() && !m_alpha->isBeingAnimated();\n}\n\nint CLayerSurface::popupsCount() {\n    if (!m_layerSurface || !m_mapped || m_fadingOut)\n        return 0;\n\n    int no = -1; // we have one dummy\n    m_popupHead->breadthfirst([](WP<Desktop::View::CPopup> p, void* data) { *sc<int*>(data) += 1; }, &no);\n    return no;\n}\n\nMONITORID CLayerSurface::monitorID() {\n    return m_monitor ? m_monitor->m_id : MONITOR_INVALID;\n}\n\npid_t CLayerSurface::getPID() {\n    pid_t PID = -1;\n\n    if (!m_layerSurface || !m_layerSurface->m_surface || !m_layerSurface->m_surface->getResource() || !m_layerSurface->m_surface->getResource()->resource() ||\n        !m_layerSurface->m_surface->getResource()->client())\n        return -1;\n\n    wl_client_get_credentials(m_layerSurface->m_surface->getResource()->client(), &PID, nullptr, nullptr);\n\n    return PID;\n}\n"
  },
  {
    "path": "src/desktop/view/LayerSurface.hpp",
    "content": "#pragma once\n\n#include <string>\n#include \"../../defines.hpp\"\n#include \"WLSurface.hpp\"\n#include \"View.hpp\"\n#include \"../rule/layerRule/LayerRuleApplicator.hpp\"\n#include \"../../helpers/AnimatedVariable.hpp\"\n#include \"../../render/Framebuffer.hpp\"\n\nclass CLayerShellResource;\n\nnamespace Desktop::View {\n\n    class CLayerSurface : public IView {\n      public:\n        static PHLLS create(SP<CLayerShellResource>);\n        static PHLLS fromView(SP<IView>);\n\n      private:\n        CLayerSurface(SP<CLayerShellResource>);\n\n      public:\n        virtual ~CLayerSurface();\n\n        virtual eViewType           type() const;\n        virtual bool                visible() const;\n        virtual std::optional<CBox> logicalBox() const;\n        virtual bool                desktopComponent() const;\n        virtual std::optional<CBox> surfaceLogicalBox() const;\n\n        bool                        isFadedOut();\n        int                         popupsCount();\n\n        PHLANIMVAR<Vector2D>        m_realPosition;\n        PHLANIMVAR<Vector2D>        m_realSize;\n        PHLANIMVAR<float>           m_alpha;\n\n        WP<CLayerShellResource>     m_layerSurface;\n\n        // the header providing the enum type cannot be imported here\n        int                                     m_interactivity = 0;\n\n        bool                                    m_mapped = false;\n        uint32_t                                m_layer  = 0;\n\n        PHLMONITORREF                           m_monitor;\n\n        bool                                    m_fadingOut       = false;\n        bool                                    m_readyToDelete   = false;\n        bool                                    m_noProcess       = false;\n        bool                                    m_aboveFullscreen = true;\n\n        UP<Desktop::Rule::CLayerRuleApplicator> m_ruleApplicator;\n\n        PHLLSREF                                m_self;\n\n        CBox                                    m_geometry = {0, 0, 0, 0};\n        Vector2D                                m_position;\n        std::string                             m_namespace = \"\";\n        SP<Desktop::View::CPopup>               m_popupHead;\n\n        SP<IFramebuffer>                        m_snapshotFB;\n\n        pid_t                                   getPID();\n\n        void                                    onDestroy();\n        void                                    onMap();\n        void                                    onUnmap();\n        void                                    onCommit();\n        MONITORID                               monitorID();\n\n      private:\n        struct {\n            CHyprSignalListener destroy;\n            CHyprSignalListener map;\n            CHyprSignalListener unmap;\n            CHyprSignalListener commit;\n        } m_listeners;\n\n        void registerCallbacks();\n\n        // For the list lookup\n        bool operator==(const CLayerSurface& rhs) const {\n            return m_layerSurface == rhs.m_layerSurface && m_monitor == rhs.m_monitor;\n        }\n    };\n\n    inline bool valid(PHLLS l) {\n        return l;\n    }\n\n    inline bool valid(PHLLSREF l) {\n        return l;\n    }\n\n    inline bool validMapped(PHLLS l) {\n        if (!valid(l))\n            return false;\n        return l->aliveAndVisible();\n    }\n\n    inline bool validMapped(PHLLSREF l) {\n        if (!valid(l))\n            return false;\n        return l->aliveAndVisible();\n    }\n\n}\n"
  },
  {
    "path": "src/desktop/view/Popup.cpp",
    "content": "#include \"Popup.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../protocols/LayerShell.hpp\"\n#include \"../../protocols/XDGShell.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../managers/animation/AnimationManager.hpp\"\n#include \"LayerSurface.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../render/OpenGL.hpp\"\n#include <ranges>\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nSP<CPopup> CPopup::create(PHLWINDOW pOwner) {\n    auto popup           = SP<CPopup>(new CPopup());\n    popup->m_windowOwner = pOwner;\n    popup->m_self        = popup;\n    popup->initAllSignals();\n    return popup;\n}\n\nSP<CPopup> CPopup::create(PHLLS pOwner) {\n    auto popup          = SP<CPopup>(new CPopup());\n    popup->m_layerOwner = pOwner;\n    popup->m_self       = popup;\n    popup->initAllSignals();\n    return popup;\n}\n\nSP<CPopup> CPopup::create(SP<CXDGPopupResource> resource, WP<CPopup> pOwner) {\n    auto popup           = SP<CPopup>(new CPopup());\n    popup->m_resource    = resource;\n    popup->m_windowOwner = pOwner->m_windowOwner;\n    popup->m_layerOwner  = pOwner->m_layerOwner;\n    popup->m_parent      = pOwner;\n    popup->m_self        = popup;\n    popup->wlSurface()->assign(resource->m_surface->m_surface.lock(), popup);\n\n    popup->m_lastSize = resource->m_surface->m_current.geometry.size();\n    popup->reposition();\n\n    popup->initAllSignals();\n    return popup;\n}\n\nSP<CPopup> CPopup::fromView(SP<IView> v) {\n    if (!v || v->type() != VIEW_TYPE_POPUP)\n        return nullptr;\n    return dynamicPointerCast<CPopup>(v);\n}\n\nCPopup::CPopup() : IView(CWLSurface::create()) {\n    ;\n}\n\nCPopup::~CPopup() {\n    if (m_wlSurface)\n        m_wlSurface->unassign();\n}\n\neViewType CPopup::type() const {\n    return VIEW_TYPE_POPUP;\n}\n\nbool CPopup::visible() const {\n    if ((!m_mapped || !m_wlSurface->resource()) && (!m_fadingOut || m_alpha->value() > 0.F))\n        return false;\n\n    if (!m_windowOwner.expired())\n        return g_pHyprRenderer->shouldRenderWindow(m_windowOwner.lock());\n\n    if (!m_layerOwner.expired())\n        return true;\n\n    if (m_parent)\n        return m_parent->visible();\n\n    return false;\n}\n\nstd::optional<CBox> CPopup::logicalBox() const {\n    return surfaceLogicalBox();\n}\n\nstd::optional<CBox> CPopup::surfaceLogicalBox() const {\n    if (!visible())\n        return std::nullopt;\n\n    return CBox{coordsGlobal(), size()};\n}\n\nbool CPopup::desktopComponent() const {\n    return true;\n}\n\nvoid CPopup::initAllSignals() {\n\n    g_pAnimationManager->createAnimation(0.f, m_alpha, g_pConfigManager->getAnimationPropertyConfig(\"fadePopupsIn\"), AVARDAMAGE_NONE);\n    m_alpha->setUpdateCallback([this](auto) {\n        //\n        g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()});\n    });\n    m_alpha->setCallbackOnEnd(\n        [this](auto) {\n            if (inert()) {\n                g_pEventLoopManager->doLater([p = m_self] {\n                    if (!p)\n                        return;\n                    g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()});\n                    p->fullyDestroy();\n                });\n            }\n        },\n        false);\n\n    if (!m_resource) {\n        if (!m_windowOwner.expired())\n            m_listeners.newPopup = m_windowOwner->m_xdgSurface->m_events.newPopup.listen([this](const auto& resource) { this->onNewPopup(resource); });\n        else if (!m_layerOwner.expired())\n            m_listeners.newPopup = m_layerOwner->m_layerSurface->m_events.newPopup.listen([this](const auto& resource) { this->onNewPopup(resource); });\n        else\n            ASSERT(false);\n\n        return;\n    }\n\n    m_listeners.reposition = m_resource->m_events.reposition.listen([this] { this->onReposition(); });\n    m_listeners.map        = m_resource->m_surface->m_events.map.listen([this] { this->onMap(); });\n    m_listeners.unmap      = m_resource->m_surface->m_events.unmap.listen([this] { this->onUnmap(); });\n    m_listeners.dismissed  = m_resource->m_events.dismissed.listen([this] { this->onUnmap(); });\n    m_listeners.destroy    = m_resource->m_events.destroy.listen([this] { this->onDestroy(); });\n    m_listeners.commit     = m_resource->m_surface->m_events.commit.listen([this] { this->onCommit(); });\n    m_listeners.newPopup   = m_resource->m_surface->m_events.newPopup.listen([this](const auto& resource) { this->onNewPopup(resource); });\n}\n\nvoid CPopup::onNewPopup(SP<CXDGPopupResource> popup) {\n    const auto& POPUP = m_children.emplace_back(CPopup::create(popup, m_self));\n    POPUP->m_self     = POPUP;\n    Log::logger->log(Log::DEBUG, \"New popup at {:x}\", rc<uintptr_t>(this));\n}\n\nvoid CPopup::onDestroy() {\n    m_inert = true;\n\n    if (!m_parent)\n        return; // head node\n\n    m_subsurfaceHead.reset();\n    m_children.clear();\n    m_wlSurface.reset();\n\n    m_listeners.map.reset();\n    m_listeners.unmap.reset();\n    m_listeners.commit.reset();\n    m_listeners.newPopup.reset();\n\n    if (m_fadingOut && m_alpha->isBeingAnimated()) {\n        Log::logger->log(Log::DEBUG, \"popup {:x}: skipping full destroy, animating\", rc<uintptr_t>(this));\n        return;\n    }\n\n    fullyDestroy();\n}\n\nvoid CPopup::fullyDestroy() {\n    Log::logger->log(Log::DEBUG, \"popup {:x} fully destroying\", rc<uintptr_t>(this));\n\n    std::erase_if(m_parent->m_children, [this](const auto& other) { return other.get() == this; });\n}\n\nvoid CPopup::onMap() {\n    if (m_mapped)\n        return;\n\n    m_mapped   = true;\n    m_lastSize = m_resource->m_surface->m_surface->m_current.size;\n\n    const auto COORDS   = coordsGlobal();\n    const auto PMONITOR = g_pCompositor->getMonitorFromVector(COORDS);\n\n    CBox       box = m_wlSurface->resource()->extends();\n    box.translate(COORDS).expand(4);\n    g_pHyprRenderer->damageBox(box);\n\n    m_lastPos = coordsRelativeToParent();\n\n    g_pInputManager->simulateMouseMovement();\n\n    m_subsurfaceHead = CSubsurface::create(m_self);\n\n    //unconstrain();\n    sendScale();\n    m_wlSurface->resource()->breadthfirst([PMONITOR](SP<CWLSurfaceResource> s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr);\n\n    if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) {\n        if (m_layerOwner->m_monitor)\n            m_layerOwner->m_monitor->m_blurFBDirty = true;\n    }\n\n    m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadePopupsIn\"));\n    m_alpha->setValueAndWarp(0.F);\n    *m_alpha = 1.F;\n\n    Log::logger->log(Log::DEBUG, \"popup {:x}: mapped\", rc<uintptr_t>(this));\n}\n\nvoid CPopup::onUnmap() {\n    if (!m_mapped)\n        return;\n\n    if (!m_resource || !m_resource->m_surface) {\n        Log::logger->log(Log::ERR, \"CPopup: orphaned (no surface/resource) and unmaps??\");\n        onDestroy();\n        return;\n    }\n\n    Log::logger->log(Log::DEBUG, \"popup {:x}: unmapped\", rc<uintptr_t>(this));\n\n    // if the popup committed a different size right now, we also need to damage the old size.\n    const Vector2D MAX_DAMAGE_SIZE = {std::max(m_lastSize.x, m_resource->m_surface->m_surface->m_current.size.x),\n                                      std::max(m_lastSize.y, m_resource->m_surface->m_surface->m_current.size.y)};\n\n    m_lastSize = m_resource->m_surface->m_surface->m_current.size;\n    m_lastPos  = coordsRelativeToParent();\n\n    const auto COORDS = coordsGlobal();\n\n    CBox       box = m_wlSurface->resource()->extends();\n    box.translate(COORDS).expand(4);\n    g_pHyprRenderer->damageBox(box);\n\n    // damage the last popup's explicit max size as well\n    box = CBox{COORDS, MAX_DAMAGE_SIZE}.expand(4);\n    g_pHyprRenderer->damageBox(box);\n\n    m_lastSize = MAX_DAMAGE_SIZE;\n\n    g_pHyprRenderer->makeSnapshot(m_self);\n\n    m_fadingOut = true;\n    m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadePopupsOut\"));\n    m_alpha->setValueAndWarp(1.F);\n    *m_alpha = 0.F;\n\n    m_mapped = false;\n\n    m_subsurfaceHead.reset();\n\n    if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) {\n        if (m_layerOwner->m_monitor)\n            m_layerOwner->m_monitor->m_blurFBDirty = true;\n    }\n\n    // damage all children\n    breadthfirst(\n        [](WP<CPopup> p, void* data) {\n            if (!p->m_resource)\n                return;\n\n            auto box = CBox{p->coordsGlobal(), p->size()};\n            g_pHyprRenderer->damageBox(box);\n        },\n        nullptr);\n\n    // TODO: probably refocus, but without a motion event?\n    // const bool WASLASTFOCUS = g_pSeatManager->state.keyboardFocus == m_pWLSurface->resource() || g_pSeatManager->state.pointerFocus == m_pWLSurface->resource();\n\n    // if (WASLASTFOCUS)\n    //     g_pInputManager->simulateMouseMovement();\n}\n\nvoid CPopup::onCommit(bool ignoreSiblings) {\n    if (!m_resource || !m_resource->m_surface) {\n        Log::logger->log(Log::ERR, \"CPopup: orphaned (no surface/resource) and commits??\");\n        onDestroy();\n        return;\n    }\n\n    if (m_resource->m_surface->m_initialCommit) {\n        m_resource->m_surface->scheduleConfigure();\n        return;\n    }\n\n    if (!m_windowOwner.expired() && (!m_windowOwner->m_isMapped || !m_windowOwner->m_workspace->m_visible)) {\n        m_lastSize = m_resource->m_surface->m_surface->m_current.size;\n\n        static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>(\"debug:log_damage\");\n        if (*PLOGDAMAGE)\n            Log::logger->log(Log::DEBUG, \"Refusing to commit damage from a subsurface of {} because it's invisible.\", m_windowOwner.lock());\n        return;\n    }\n\n    if (!m_resource->m_surface->m_mapped)\n        return;\n\n    const auto COORDS      = coordsGlobal();\n    const auto COORDSLOCAL = coordsRelativeToParent();\n\n    if (m_lastSize != m_resource->m_surface->m_surface->m_current.size || m_requestedReposition || m_lastPos != COORDSLOCAL) {\n        CBox box = {localToGlobal(m_lastPos), m_lastSize};\n        g_pHyprRenderer->damageBox(box);\n        m_lastSize = m_resource->m_surface->m_surface->m_current.size;\n        box        = {COORDS, m_lastSize};\n        g_pHyprRenderer->damageBox(box);\n\n        m_lastPos = COORDSLOCAL;\n    }\n\n    if (!ignoreSiblings && m_subsurfaceHead)\n        m_subsurfaceHead->recheckDamageForSubsurfaces();\n\n    g_pHyprRenderer->damageSurface(m_wlSurface->resource(), COORDS.x, COORDS.y);\n\n    m_requestedReposition = false;\n\n    if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) {\n        if (m_layerOwner->m_monitor)\n            m_layerOwner->m_monitor->m_blurFBDirty = true;\n    }\n}\n\nvoid CPopup::onReposition() {\n    Log::logger->log(Log::DEBUG, \"Popup {:x} requests reposition\", rc<uintptr_t>(this));\n\n    m_requestedReposition = true;\n\n    m_lastPos = coordsRelativeToParent();\n\n    reposition();\n}\n\nvoid CPopup::reposition() {\n    const auto COORDS   = t1ParentCoords();\n    const auto PMONITOR = g_pCompositor->getMonitorFromVector(COORDS);\n\n    if (!PMONITOR)\n        return;\n\n    m_resource->applyPositioning(m_windowOwner ? PMONITOR->logicalBoxMinusReserved() : PMONITOR->logicalBox(), COORDS);\n}\n\nSP<Desktop::View::CWLSurface> CPopup::getT1Owner() const {\n    if (m_windowOwner)\n        return m_windowOwner->wlSurface();\n    else\n        return m_layerOwner->wlSurface();\n}\n\nVector2D CPopup::coordsRelativeToParent() const {\n    Vector2D offset;\n\n    if (!m_resource)\n        return m_lastPos;\n\n    WP<CPopup> current = m_self;\n    offset -= current->m_resource->m_surface->m_current.geometry.pos();\n\n    while (current->m_parent && current->m_resource) {\n\n        offset += current->wlSurface()->resource()->m_current.offset;\n        offset += current->m_resource->m_geometry.pos();\n\n        current = current->m_parent;\n    }\n\n    return offset;\n}\n\nVector2D CPopup::coordsGlobal() const {\n    return localToGlobal(coordsRelativeToParent());\n}\n\nVector2D CPopup::localToGlobal(const Vector2D& rel) const {\n    return t1ParentCoords() + rel;\n}\n\nVector2D CPopup::t1ParentCoords() const {\n    if (!m_windowOwner.expired())\n        return m_windowOwner->m_realPosition->value();\n    if (!m_layerOwner.expired())\n        return m_layerOwner->m_realPosition->value();\n\n    ASSERT(false);\n    return {};\n}\n\nvoid CPopup::recheckTree() {\n    WP<CPopup> curr = m_self;\n    while (curr->m_parent) {\n        curr = curr->m_parent;\n    }\n\n    curr->recheckChildrenRecursive();\n}\n\nvoid CPopup::recheckChildrenRecursive() {\n    if (m_inert || !m_wlSurface)\n        return;\n\n    std::vector<WP<CPopup>> cpy;\n    std::ranges::for_each(m_children, [&cpy](const auto& el) { cpy.emplace_back(el); });\n    for (auto const& c : cpy) {\n        if (!c || !c->visible())\n            continue;\n\n        // keep ref, onCommit can call onDestroy\n        auto x = c.lock();\n\n        x->onCommit(true);\n        x->recheckChildrenRecursive();\n    }\n}\n\nVector2D CPopup::size() const {\n    return m_lastSize;\n}\n\nvoid CPopup::sendScale() {\n    if (!m_windowOwner.expired())\n        g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), m_windowOwner->wlSurface()->m_lastScaleFloat);\n    else if (!m_layerOwner.expired())\n        g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), m_layerOwner->wlSurface()->m_lastScaleFloat);\n    else\n        UNREACHABLE();\n}\n\nvoid CPopup::bfHelper(std::vector<SP<CPopup>> const& nodes, std::function<void(SP<CPopup>, void*)> fn, void* data) {\n    for (auto const& n : nodes) {\n        fn(n, data);\n    }\n\n    std::vector<SP<CPopup>> nodes2;\n    nodes2.reserve(nodes.size() * 2);\n\n    for (auto const& n : nodes) {\n        if (!n)\n            continue;\n\n        for (auto const& c : n->m_children) {\n            nodes2.emplace_back(c->m_self.lock());\n        }\n    }\n\n    if (!nodes2.empty())\n        bfHelper(nodes2, fn, data);\n}\n\nvoid CPopup::breadthfirst(std::function<void(SP<CPopup>, void*)> fn, void* data) {\n    if (!m_self)\n        return;\n\n    std::vector<SP<CPopup>> popups;\n    popups.emplace_back(m_self.lock());\n    bfHelper(popups, fn, data);\n}\n\nSP<CPopup> CPopup::at(const Vector2D& globalCoords, bool allowsInput) {\n    std::vector<SP<CPopup>> popups;\n    breadthfirst([&popups](SP<CPopup> popup, void* data) { popups.push_back(popup); }, &popups);\n\n    for (auto const& p : popups | std::views::reverse) {\n        if (!p->m_resource || !p->m_mapped)\n            continue;\n\n        if (!allowsInput) {\n            const bool HASSURFACE = p->m_resource && p->m_resource->m_surface;\n\n            Vector2D   offset = HASSURFACE ? p->m_resource->m_surface->m_current.geometry.pos() : Vector2D{};\n            Vector2D   size   = HASSURFACE ? p->m_resource->m_surface->m_current.geometry.size() : p->size();\n\n            if (size == Vector2D{})\n                size = p->size();\n\n            const auto BOX = CBox{p->coordsGlobal() + offset, size};\n            if (BOX.containsPoint(globalCoords))\n                return p;\n        } else {\n            const auto REGION = CRegion{p->wlSurface()->resource()->m_current.input}.intersect(CBox{{}, p->wlSurface()->resource()->m_current.size}).translate(p->coordsGlobal());\n            if (REGION.containsPoint(globalCoords))\n                return p;\n        }\n    }\n\n    return {};\n}\n\nbool CPopup::inert() const {\n    return m_inert;\n}\n\nPHLMONITOR CPopup::getMonitor() const {\n    if (!m_windowOwner.expired())\n        return m_windowOwner->m_monitor.lock();\n    if (!m_layerOwner.expired())\n        return m_layerOwner->m_monitor.lock();\n    return nullptr;\n}\n"
  },
  {
    "path": "src/desktop/view/Popup.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"Subsurface.hpp\"\n#include \"View.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/AnimatedVariable.hpp\"\n#include \"../../render/Framebuffer.hpp\"\n\nclass CXDGPopupResource;\n\nnamespace Desktop::View {\n\n    class CPopup : public IView {\n      public:\n        // dummy head nodes\n        static SP<CPopup> create(PHLWINDOW pOwner);\n        static SP<CPopup> create(PHLLS pOwner);\n\n        // real nodes\n        static SP<CPopup> create(SP<CXDGPopupResource> popup, WP<CPopup> pOwner);\n\n        static SP<CPopup> fromView(SP<IView>);\n\n        virtual ~CPopup();\n\n        virtual eViewType             type() const;\n        virtual bool                  visible() const;\n        virtual std::optional<CBox>   logicalBox() const;\n        virtual bool                  desktopComponent() const;\n        virtual std::optional<CBox>   surfaceLogicalBox() const;\n\n        SP<Desktop::View::CWLSurface> getT1Owner() const;\n        Vector2D                      coordsRelativeToParent() const;\n        Vector2D                      coordsGlobal() const;\n        PHLMONITOR                    getMonitor() const;\n\n        Vector2D                      size() const;\n\n        void                          onNewPopup(SP<CXDGPopupResource> popup);\n        void                          onDestroy();\n        void                          onMap();\n        void                          onUnmap();\n        void                          onCommit(bool ignoreSiblings = false);\n        void                          onReposition();\n\n        void                          recheckTree();\n\n        bool                          inert() const;\n\n        // will also loop over this node\n        void                      breadthfirst(std::function<void(SP<Desktop::View::CPopup>, void*)> fn, void* data);\n        SP<Desktop::View::CPopup> at(const Vector2D& globalCoords, bool allowsInput = false);\n\n        //\n        WP<Desktop::View::CPopup> m_self;\n        bool                      m_mapped = false;\n\n        // fade in-out\n        PHLANIMVAR<float> m_alpha;\n        bool              m_fadingOut = false;\n\n        SP<IFramebuffer>  m_snapshotFB;\n\n      private:\n        CPopup();\n\n        // T1 owners, each popup has to have one of these\n        PHLWINDOWREF m_windowOwner;\n        PHLLSREF     m_layerOwner;\n\n        // T2 owners\n        WP<Desktop::View::CPopup> m_parent;\n\n        WP<CXDGPopupResource>     m_resource;\n\n        Vector2D                  m_lastSize = {};\n        Vector2D                  m_lastPos  = {};\n\n        bool                      m_requestedReposition = false;\n\n        bool                      m_inert = false;\n\n        //\n        std::vector<SP<Desktop::View::CPopup>> m_children;\n        SP<Desktop::View::CSubsurface>         m_subsurfaceHead;\n\n        struct {\n            CHyprSignalListener newPopup;\n            CHyprSignalListener destroy;\n            CHyprSignalListener map;\n            CHyprSignalListener unmap;\n            CHyprSignalListener commit;\n            CHyprSignalListener dismissed;\n            CHyprSignalListener reposition;\n        } m_listeners;\n\n        void        initAllSignals();\n        void        reposition();\n        void        recheckChildrenRecursive();\n        void        sendScale();\n        void        fullyDestroy();\n\n        Vector2D    localToGlobal(const Vector2D& rel) const;\n        Vector2D    t1ParentCoords() const;\n        static void bfHelper(std::vector<SP<CPopup>> const& nodes, std::function<void(SP<CPopup>, void*)> fn, void* data);\n    };\n}\n"
  },
  {
    "path": "src/desktop/view/SessionLock.cpp",
    "content": "#include \"SessionLock.hpp\"\n\n#include \"../../protocols/SessionLock.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n\n#include \"../../Compositor.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nSP<View::CSessionLock> View::CSessionLock::create(SP<CSessionLockSurface> resource) {\n    auto lock       = SP<CSessionLock>(new CSessionLock());\n    lock->m_surface = resource;\n    lock->m_self    = lock;\n\n    lock->init();\n\n    return lock;\n}\n\nView::CSessionLock::CSessionLock() : IView(CWLSurface::create()) {\n    ;\n}\n\nView::CSessionLock::~CSessionLock() {\n    m_wlSurface->unassign();\n}\n\nvoid View::CSessionLock::init() {\n    m_listeners.destroy = m_surface->m_events.destroy.listen([this] { std::erase_if(g_pCompositor->m_otherViews, [this](const auto& e) { return e == m_self; }); });\n\n    m_wlSurface->assign(m_surface->surface(), m_self.lock());\n}\n\nSP<View::CSessionLock> View::CSessionLock::fromView(SP<IView> v) {\n    if (!v || v->type() != VIEW_TYPE_LOCK_SCREEN)\n        return nullptr;\n    return dynamicPointerCast<View::CSessionLock>(v);\n}\n\neViewType View::CSessionLock::type() const {\n    return VIEW_TYPE_LOCK_SCREEN;\n}\n\nbool View::CSessionLock::visible() const {\n    return m_wlSurface && m_wlSurface->resource() && m_wlSurface->resource()->m_mapped;\n}\n\nstd::optional<CBox> View::CSessionLock::logicalBox() const {\n    return surfaceLogicalBox();\n}\n\nstd::optional<CBox> View::CSessionLock::surfaceLogicalBox() const {\n    if (!visible())\n        return std::nullopt;\n\n    const auto MON = m_surface->monitor();\n\n    if (!MON)\n        return std::nullopt;\n\n    return MON->logicalBox();\n}\n\nbool View::CSessionLock::desktopComponent() const {\n    return true;\n}\n\nPHLMONITOR View::CSessionLock::monitor() const {\n    if (m_surface)\n        return m_surface->monitor();\n    return nullptr;\n}\n"
  },
  {
    "path": "src/desktop/view/SessionLock.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include <vector>\n#include \"WLSurface.hpp\"\n#include \"View.hpp\"\n\nclass CSessionLockSurface;\n\nnamespace Desktop::View {\n    class CSessionLock : public IView {\n      public:\n        static SP<CSessionLock> create(SP<CSessionLockSurface> resource);\n\n        static SP<CSessionLock> fromView(SP<IView>);\n\n        virtual ~CSessionLock();\n\n        virtual eViewType           type() const;\n        virtual bool                visible() const;\n        virtual std::optional<CBox> logicalBox() const;\n        virtual bool                desktopComponent() const;\n        virtual std::optional<CBox> surfaceLogicalBox() const;\n\n        PHLMONITOR                  monitor() const;\n\n        WP<CSessionLock>            m_self;\n\n      private:\n        CSessionLock();\n\n        void init();\n\n        struct {\n            CHyprSignalListener destroy;\n        } m_listeners;\n\n        WP<CSessionLockSurface> m_surface;\n    };\n}\n"
  },
  {
    "path": "src/desktop/view/Subsurface.cpp",
    "content": "#include \"Subsurface.hpp\"\n#include \"../state/FocusState.hpp\"\n#include \"Window.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../protocols/core/Subcompositor.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nSP<CSubsurface> CSubsurface::create(PHLWINDOW pOwner) {\n    auto subsurface            = SP<CSubsurface>(new CSubsurface());\n    subsurface->m_windowParent = pOwner;\n    subsurface->m_self         = subsurface;\n\n    subsurface->initSignals();\n    subsurface->initExistingSubsurfaces(pOwner->wlSurface()->resource());\n    return subsurface;\n}\n\nSP<CSubsurface> CSubsurface::create(WP<Desktop::View::CPopup> pOwner) {\n    auto subsurface           = SP<CSubsurface>(new CSubsurface());\n    subsurface->m_popupParent = pOwner;\n    subsurface->m_self        = subsurface;\n    subsurface->initSignals();\n    subsurface->initExistingSubsurfaces(pOwner->wlSurface()->resource());\n    return subsurface;\n}\n\nSP<CSubsurface> CSubsurface::create(SP<CWLSubsurfaceResource> pSubsurface, PHLWINDOW pOwner) {\n    auto subsurface            = SP<CSubsurface>(new CSubsurface());\n    subsurface->m_windowParent = pOwner;\n    subsurface->m_subsurface   = pSubsurface;\n    subsurface->m_self         = subsurface;\n    subsurface->wlSurface()    = CWLSurface::create();\n    subsurface->wlSurface()->assign(pSubsurface->m_surface.lock(), subsurface);\n    subsurface->initSignals();\n    subsurface->initExistingSubsurfaces(pSubsurface->m_surface.lock());\n    return subsurface;\n}\n\nSP<CSubsurface> CSubsurface::create(SP<CWLSubsurfaceResource> pSubsurface, WP<Desktop::View::CPopup> pOwner) {\n    auto subsurface           = SP<CSubsurface>(new CSubsurface());\n    subsurface->m_popupParent = pOwner;\n    subsurface->m_subsurface  = pSubsurface;\n    subsurface->m_self        = subsurface;\n    subsurface->wlSurface()   = CWLSurface::create();\n    subsurface->wlSurface()->assign(pSubsurface->m_surface.lock(), subsurface);\n    subsurface->initSignals();\n    subsurface->initExistingSubsurfaces(pSubsurface->m_surface.lock());\n    return subsurface;\n}\n\nSP<CSubsurface> CSubsurface::fromView(SP<IView> v) {\n    if (!v || v->type() != VIEW_TYPE_SUBSURFACE)\n        return nullptr;\n    return dynamicPointerCast<CSubsurface>(v);\n}\n\nCSubsurface::CSubsurface() : IView(CWLSurface::create()) {\n    ;\n}\n\neViewType CSubsurface::type() const {\n    return VIEW_TYPE_SUBSURFACE;\n}\n\nbool CSubsurface::visible() const {\n    if (!m_wlSurface || !m_wlSurface->resource() || !m_wlSurface->resource()->m_mapped)\n        return false;\n\n    if (!m_windowParent.expired())\n        return g_pHyprRenderer->shouldRenderWindow(m_windowParent.lock());\n    if (m_popupParent)\n        return m_popupParent->visible();\n    if (m_parent)\n        return m_parent->visible();\n\n    return false;\n}\n\nbool CSubsurface::desktopComponent() const {\n    return true;\n}\n\nstd::optional<CBox> CSubsurface::logicalBox() const {\n    return surfaceLogicalBox();\n}\n\nstd::optional<CBox> CSubsurface::surfaceLogicalBox() const {\n    if (!visible())\n        return std::nullopt;\n\n    return CBox{coordsGlobal(), m_lastSize};\n}\n\nvoid CSubsurface::initSignals() {\n    if (m_subsurface) {\n        m_listeners.commitSubsurface  = m_subsurface->m_surface->m_events.commit.listen([this] { onCommit(); });\n        m_listeners.destroySubsurface = m_subsurface->m_events.destroy.listen([this] { onDestroy(); });\n        m_listeners.mapSubsurface     = m_subsurface->m_surface->m_events.map.listen([this] { onMap(); });\n        m_listeners.unmapSubsurface   = m_subsurface->m_surface->m_events.unmap.listen([this] { onUnmap(); });\n        m_listeners.newSubsurface     = m_subsurface->m_surface->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); });\n    } else {\n        if (m_windowParent)\n            m_listeners.newSubsurface = m_windowParent->wlSurface()->resource()->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); });\n        else if (m_popupParent)\n            m_listeners.newSubsurface = m_popupParent->wlSurface()->resource()->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); });\n        else\n            ASSERT(false);\n    }\n}\n\nvoid CSubsurface::checkSiblingDamage() {\n    if (!m_parent)\n        return; // ??????????\n\n    const double SCALE = m_windowParent.lock() && m_windowParent->m_isX11 ? 1.0 / m_windowParent->m_X11SurfaceScaledBy : 1.0;\n\n    for (auto const& n : m_parent->m_children) {\n        if (n.get() == this)\n            continue;\n\n        const auto COORDS = n->coordsGlobal();\n        g_pHyprRenderer->damageSurface(n->wlSurface()->resource(), COORDS.x, COORDS.y, SCALE);\n    }\n}\n\nvoid CSubsurface::recheckDamageForSubsurfaces() {\n    for (auto const& n : m_children) {\n        const auto COORDS = n->coordsGlobal();\n        g_pHyprRenderer->damageSurface(n->wlSurface()->resource(), COORDS.x, COORDS.y);\n    }\n}\n\nvoid CSubsurface::onCommit() {\n    // no damaging if it's not visible\n    if (!m_windowParent.expired() && (!m_windowParent->m_isMapped || !m_windowParent->m_workspace->m_visible)) {\n        m_lastSize = m_wlSurface->resource()->m_current.size;\n\n        static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>(\"debug:log_damage\");\n        if (*PLOGDAMAGE)\n            Log::logger->log(Log::DEBUG, \"Refusing to commit damage from a subsurface of {} because it's invisible.\", m_windowParent.lock());\n        return;\n    }\n\n    const auto COORDS = coordsGlobal();\n\n    g_pHyprRenderer->damageSurface(m_wlSurface->resource(), COORDS.x, COORDS.y);\n\n    if (m_popupParent && !m_popupParent->inert() && m_popupParent->wlSurface())\n        m_popupParent->recheckTree();\n    if (!m_windowParent.expired()) // I hate you firefox why are you doing this\n        m_windowParent->m_popupHead->recheckTree();\n\n    // I do not think this is correct, but it solves a lot of issues with some apps (e.g. firefox)\n    checkSiblingDamage();\n\n    if (m_lastSize != m_wlSurface->resource()->m_current.size || m_lastPosition != m_subsurface->m_position) {\n        damageLastArea();\n        m_lastSize     = m_wlSurface->resource()->m_current.size;\n        m_lastPosition = m_subsurface->m_position;\n    }\n}\n\nvoid CSubsurface::onDestroy() {\n    // destroy children\n    m_children.clear();\n\n    m_inert = true;\n\n    if (!m_subsurface)\n        return; // dummy node, nothing to do, it's the parent dying\n\n    // kill ourselves\n    std::erase_if(m_parent->m_children, [this](const auto& other) { return other.get() == this; });\n}\n\nvoid CSubsurface::onNewSubsurface(SP<CWLSubsurfaceResource> pSubsurface) {\n    WP<CSubsurface> PSUBSURFACE;\n\n    if (!m_windowParent.expired())\n        PSUBSURFACE = m_children.emplace_back(CSubsurface::create(pSubsurface, m_windowParent.lock()));\n    else if (m_popupParent)\n        PSUBSURFACE = m_children.emplace_back(CSubsurface::create(pSubsurface, m_popupParent));\n\n    PSUBSURFACE->m_self = PSUBSURFACE;\n\n    ASSERT(PSUBSURFACE);\n\n    PSUBSURFACE->m_parent = m_self;\n}\n\nvoid CSubsurface::onMap() {\n    m_lastSize     = m_wlSurface->resource()->m_current.size;\n    m_lastPosition = m_subsurface->m_position;\n\n    const auto COORDS = coordsGlobal();\n    CBox       box{COORDS, m_lastSize};\n    box.expand(4);\n    g_pHyprRenderer->damageBox(box);\n\n    if (!m_windowParent.expired())\n        m_windowParent->updateSurfaceScaleTransformDetails();\n}\n\nvoid CSubsurface::onUnmap() {\n    damageLastArea();\n\n    if (m_wlSurface->resource() == Desktop::focusState()->surface())\n        g_pInputManager->releaseAllMouseButtons();\n\n    g_pInputManager->simulateMouseMovement();\n\n    // TODO: should this remove children? Currently it won't, only on .destroy\n}\n\nvoid CSubsurface::damageLastArea() {\n    const auto     COORDS = coordsGlobal() + m_lastPosition - m_subsurface->m_position;\n\n    const Vector2D MAX_DAMAGE_SIZE = m_wlSurface && m_wlSurface->resource() ?\n        Vector2D{\n            std::max(m_lastSize.x, m_wlSurface->resource()->m_current.size.x),\n            std::max(m_lastSize.y, m_wlSurface->resource()->m_current.size.y),\n        } :\n        m_lastSize;\n\n    CBox           box{COORDS, m_lastSize};\n    box.expand(4);\n    g_pHyprRenderer->damageBox(box);\n}\n\nVector2D CSubsurface::coordsRelativeToParent() const {\n    if (!m_subsurface)\n        return {};\n    return m_subsurface->posRelativeToParent();\n}\n\nVector2D CSubsurface::coordsGlobal() const {\n    Vector2D coords = coordsRelativeToParent();\n\n    if (!m_windowParent.expired())\n        coords += m_windowParent->m_realPosition->value();\n    else if (m_popupParent)\n        coords += m_popupParent->coordsGlobal();\n\n    return coords;\n}\n\nvoid CSubsurface::initExistingSubsurfaces(SP<CWLSurfaceResource> pSurface) {\n    for (auto const& s : pSurface->m_subsurfaces) {\n        if (!s || s->m_surface->m_hlSurface /* already assigned */)\n            continue;\n        onNewSubsurface(s.lock());\n    }\n}\n\nVector2D CSubsurface::size() {\n    return m_wlSurface->resource()->m_current.size;\n}\n"
  },
  {
    "path": "src/desktop/view/Subsurface.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include <vector>\n#include \"WLSurface.hpp\"\n#include \"View.hpp\"\n\nclass CWLSubsurfaceResource;\n\nnamespace Desktop::View {\n    class CPopup;\n    class CSubsurface : public IView {\n      public:\n        // root dummy nodes\n        static SP<CSubsurface> create(PHLWINDOW pOwner);\n        static SP<CSubsurface> create(WP<Desktop::View::CPopup> pOwner);\n\n        // real nodes\n        static SP<CSubsurface> create(SP<CWLSubsurfaceResource> pSubsurface, PHLWINDOW pOwner);\n        static SP<CSubsurface> create(SP<CWLSubsurfaceResource> pSubsurface, WP<Desktop::View::CPopup> pOwner);\n\n        static SP<CSubsurface> fromView(SP<IView>);\n\n        virtual ~CSubsurface() = default;\n\n        virtual eViewType              type() const;\n        virtual bool                   visible() const;\n        virtual std::optional<CBox>    logicalBox() const;\n        virtual bool                   desktopComponent() const;\n        virtual std::optional<CBox>    surfaceLogicalBox() const;\n\n        Vector2D                       coordsRelativeToParent() const;\n        Vector2D                       coordsGlobal() const;\n\n        Vector2D                       size();\n\n        void                           onCommit();\n        void                           onDestroy();\n        void                           onNewSubsurface(SP<CWLSubsurfaceResource> pSubsurface);\n        void                           onMap();\n        void                           onUnmap();\n\n        void                           recheckDamageForSubsurfaces();\n\n        WP<Desktop::View::CSubsurface> m_self;\n\n      private:\n        CSubsurface();\n\n        struct {\n            CHyprSignalListener destroySubsurface;\n            CHyprSignalListener commitSubsurface;\n            CHyprSignalListener mapSubsurface;\n            CHyprSignalListener unmapSubsurface;\n            CHyprSignalListener newSubsurface;\n        } m_listeners;\n\n        WP<CWLSubsurfaceResource> m_subsurface;\n        Vector2D                  m_lastSize     = {};\n        Vector2D                  m_lastPosition = {};\n\n        // if nullptr, means it's a dummy node\n        WP<Desktop::View::CSubsurface>              m_parent;\n\n        PHLWINDOWREF                                m_windowParent;\n        WP<Desktop::View::CPopup>                   m_popupParent;\n\n        std::vector<SP<Desktop::View::CSubsurface>> m_children;\n\n        bool                                        m_inert = false;\n\n        void                                        initSignals();\n        void                                        initExistingSubsurfaces(SP<CWLSurfaceResource> pSurface);\n        void                                        checkSiblingDamage();\n        void                                        damageLastArea();\n    };\n}\n"
  },
  {
    "path": "src/desktop/view/View.cpp",
    "content": "#include \"View.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nSP<Desktop::View::CWLSurface> IView::wlSurface() const {\n    return m_wlSurface;\n}\n\nIView::IView(SP<Desktop::View::CWLSurface> pWlSurface) : m_wlSurface(pWlSurface) {\n    ;\n}\n\nSP<CWLSurfaceResource> IView::resource() const {\n    return m_wlSurface ? m_wlSurface->resource() : nullptr;\n}\n\nbool IView::aliveAndVisible() const {\n    auto res = resource();\n    if (!res)\n        return false;\n\n    if (!res->m_mapped)\n        return false;\n\n    return visible();\n}\n"
  },
  {
    "path": "src/desktop/view/View.hpp",
    "content": "#pragma once\n\n#include \"WLSurface.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n\nnamespace Desktop::View {\n    enum eViewType : uint8_t {\n        VIEW_TYPE_WINDOW = 0,\n        VIEW_TYPE_SUBSURFACE,\n        VIEW_TYPE_POPUP,\n        VIEW_TYPE_LAYER_SURFACE,\n        VIEW_TYPE_LOCK_SCREEN,\n    };\n\n    class IView {\n      public:\n        virtual ~IView() = default;\n\n        virtual SP<Desktop::View::CWLSurface> wlSurface() const;\n        virtual SP<CWLSurfaceResource>        resource() const;\n        virtual bool                          aliveAndVisible() const;\n        virtual eViewType                     type() const              = 0;\n        virtual bool                          visible() const           = 0;\n        virtual bool                          desktopComponent() const  = 0;\n        virtual std::optional<CBox>           logicalBox() const        = 0;\n        virtual std::optional<CBox>           surfaceLogicalBox() const = 0;\n\n      protected:\n        IView(SP<Desktop::View::CWLSurface> pWlSurface);\n\n        SP<Desktop::View::CWLSurface> m_wlSurface;\n    };\n};"
  },
  {
    "path": "src/desktop/view/WLSurface.cpp",
    "content": "#include \"WLSurface.hpp\"\n#include \"LayerSurface.hpp\"\n#include \"Window.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../protocols/LayerShell.hpp\"\n#include \"../../render/Renderer.hpp\"\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\nvoid CWLSurface::assign(SP<CWLSurfaceResource> pSurface) {\n    m_resource = pSurface;\n    init();\n    m_inert = false;\n}\n\nvoid CWLSurface::assign(SP<CWLSurfaceResource> pSurface, SP<IView> pOwner) {\n    m_view     = pOwner;\n    m_resource = pSurface;\n    init();\n    m_inert = false;\n}\n\nvoid CWLSurface::unassign() {\n    destroy();\n}\n\nCWLSurface::~CWLSurface() {\n    destroy();\n}\n\nbool CWLSurface::exists() const {\n    return m_resource;\n}\n\nSP<CWLSurfaceResource> CWLSurface::resource() const {\n    return m_resource.lock();\n}\n\nbool CWLSurface::small() const {\n    if (!m_view || !m_view->aliveAndVisible() || m_view->type() != VIEW_TYPE_WINDOW || !exists())\n        return false;\n\n    if (!m_resource->m_current.texture)\n        return false;\n\n    const auto O             = dynamicPointerCast<CWindow>(m_view.lock());\n    const auto REPORTED_SIZE = O->getReportedSize();\n\n    return REPORTED_SIZE.x > m_resource->m_current.size.x + 1 || REPORTED_SIZE.y > m_resource->m_current.size.y + 1;\n}\n\nVector2D CWLSurface::correctSmallVec() const {\n    if (!m_view || !m_view->aliveAndVisible() || m_view->type() != VIEW_TYPE_WINDOW || !exists() || !small() || !m_fillIgnoreSmall)\n        return {};\n\n    const auto SIZE = getViewporterCorrectedSize();\n    const auto O    = dynamicPointerCast<CWindow>(m_view.lock());\n    const auto REP  = O->getReportedSize();\n\n    return Vector2D{(REP.x - SIZE.x) / 2, (REP.y - SIZE.y) / 2}.clamp({}, {INFINITY, INFINITY}) * (O->m_realSize->value() / REP);\n}\n\nVector2D CWLSurface::correctSmallVecBuf() const {\n    if (!exists() || !small() || m_fillIgnoreSmall || !m_resource->m_current.texture)\n        return {};\n\n    const auto SIZE = getViewporterCorrectedSize();\n    const auto BS   = m_resource->m_current.bufferSize;\n\n    return Vector2D{(BS.x - SIZE.x) / 2, (BS.y - SIZE.y) / 2}.clamp({}, {INFINITY, INFINITY});\n}\n\nVector2D CWLSurface::getViewporterCorrectedSize() const {\n    if (!exists() || !m_resource->m_current.texture)\n        return {};\n\n    return m_resource->m_current.viewport.hasDestination ? m_resource->m_current.viewport.destination : m_resource->m_current.bufferSize;\n}\n\nCRegion CWLSurface::computeDamage() const {\n    if (!m_resource->m_current.texture)\n        return {};\n\n    CRegion damage = m_resource->m_current.accumulateBufferDamage();\n    damage.transform(Math::wlTransformToHyprutils(m_resource->m_current.transform), m_resource->m_current.bufferSize.x, m_resource->m_current.bufferSize.y);\n\n    const auto BUFSIZE    = m_resource->m_current.bufferSize;\n    const auto CORRECTVEC = correctSmallVecBuf();\n\n    if (m_resource->m_current.viewport.hasSource)\n        damage.intersect(m_resource->m_current.viewport.source);\n\n    const auto SCALEDSRCSIZE =\n        m_resource->m_current.viewport.hasSource ? m_resource->m_current.viewport.source.size() * m_resource->m_current.scale : m_resource->m_current.bufferSize;\n\n    damage.scale({BUFSIZE.x / SCALEDSRCSIZE.x, BUFSIZE.y / SCALEDSRCSIZE.y});\n    damage.translate(CORRECTVEC);\n\n    // go from buffer coords in the damage to hl logical\n\n    const auto     BOX      = getSurfaceBoxGlobal();\n    const auto     SURFSIZE = m_resource->m_current.size;\n    const Vector2D SCALE    = SURFSIZE / m_resource->m_current.bufferSize;\n\n    damage.scale(SCALE);\n    if (BOX.has_value()) {\n        if (m_view->type() == VIEW_TYPE_WINDOW)\n            damage.intersect(CBox{{}, BOX->size() * dynamicPointerCast<CWindow>(m_view.lock())->m_X11SurfaceScaledBy});\n        else\n            damage.intersect(CBox{{}, BOX->size()});\n    }\n\n    return damage;\n}\n\nvoid CWLSurface::destroy() {\n    if (!m_resource)\n        return;\n\n    m_events.destroy.emit();\n\n    m_constraint.reset();\n\n    m_listeners.destroy.reset();\n    m_resource->m_hlSurface.reset();\n    m_view.reset();\n    m_inert = true;\n\n    if (g_pHyprRenderer && g_pHyprRenderer->m_lastCursorData.surf && g_pHyprRenderer->m_lastCursorData.surf->get() == this)\n        g_pHyprRenderer->m_lastCursorData.surf.reset();\n\n    m_resource.reset();\n\n    Log::logger->log(Log::DEBUG, \"CWLSurface {:x} called destroy()\", rc<uintptr_t>(this));\n}\n\nvoid CWLSurface::init() {\n    if (!m_resource)\n        return;\n\n    RASSERT(!m_resource->m_hlSurface, \"Attempted to duplicate CWLSurface ownership!\");\n\n    m_resource->m_hlSurface = m_self.lock();\n\n    m_listeners.destroy = m_resource->m_events.destroy.listen([this] { destroy(); });\n\n    Log::logger->log(Log::DEBUG, \"CWLSurface {:x} called init()\", rc<uintptr_t>(this));\n}\n\nSP<IView> CWLSurface::view() const {\n    return m_view.lock();\n}\n\nbool CWLSurface::desktopComponent() const {\n    return m_view && m_view->visible();\n}\n\nstd::optional<CBox> CWLSurface::getSurfaceBoxGlobal() const {\n    if (!desktopComponent())\n        return {};\n\n    return m_view->surfaceLogicalBox();\n}\n\nvoid CWLSurface::appendConstraint(WP<CPointerConstraint> constraint) {\n    m_constraint = constraint;\n}\n\nSP<CPointerConstraint> CWLSurface::constraint() const {\n    return m_constraint.lock();\n}\n\nSP<Desktop::View::CWLSurface> CWLSurface::fromResource(SP<CWLSurfaceResource> pSurface) {\n    if (!pSurface)\n        return nullptr;\n    return pSurface->m_hlSurface.lock();\n}\n\nbool CWLSurface::keyboardFocusable() const {\n    if (!m_view)\n        return false;\n    if (m_view->type() == VIEW_TYPE_WINDOW || m_view->type() == VIEW_TYPE_SUBSURFACE || m_view->type() == VIEW_TYPE_POPUP)\n        return true;\n    if (const auto LS = CLayerSurface::fromView(m_view.lock()); LS && LS->m_layerSurface)\n        return LS->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;\n    return false;\n}\n"
  },
  {
    "path": "src/desktop/view/WLSurface.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n\nclass CPointerConstraint;\nclass CWLSurfaceResource;\n\nnamespace Desktop::View {\n    class CSubsurface;\n    class CPopup;\n    class IView;\n\n    class CWLSurface {\n      public:\n        static SP<Desktop::View::CWLSurface> create() {\n            auto p    = SP<Desktop::View::CWLSurface>(new CWLSurface);\n            p->m_self = p;\n            return p;\n        }\n        ~CWLSurface();\n\n        // anonymous surfaces are non-desktop components, e.g. a cursor surface or a DnD\n        void assign(SP<CWLSurfaceResource> pSurface);\n        void assign(SP<CWLSurfaceResource> pSurface, SP<IView> pOwner);\n        void unassign();\n\n        CWLSurface(const CWLSurface&)                       = delete;\n        CWLSurface(CWLSurface&&)                            = delete;\n        CWLSurface&            operator=(const CWLSurface&) = delete;\n        CWLSurface&            operator=(CWLSurface&&)      = delete;\n\n        SP<CWLSurfaceResource> resource() const;\n        bool                   exists() const;\n        bool                   small() const;              // means surface is smaller than the requested size\n        Vector2D               correctSmallVec() const;    // returns a corrective vector for small() surfaces\n        Vector2D               correctSmallVecBuf() const; // returns a corrective vector for small() surfaces, in BL coords\n        Vector2D               getViewporterCorrectedSize() const;\n        CRegion                computeDamage() const; // logical coordinates. May be wrong if the surface is unassigned\n        bool                   keyboardFocusable() const;\n\n        SP<IView>              view() const;\n\n        // desktop components misc utils\n        std::optional<CBox>    getSurfaceBoxGlobal() const;\n        void                   appendConstraint(WP<CPointerConstraint> constraint);\n        SP<CPointerConstraint> constraint() const;\n\n        // allow stretching. Useful for plugins.\n        bool m_fillIgnoreSmall = false;\n\n        // track surface data and avoid dupes\n        float               m_lastScaleFloat = 0;\n        int                 m_lastScaleInt   = 0;\n        wl_output_transform m_lastTransform  = sc<wl_output_transform>(-1);\n\n        //\n        CWLSurface& operator=(SP<CWLSurfaceResource> pSurface) {\n            destroy();\n            m_resource = pSurface;\n            init();\n\n            return *this;\n        }\n\n        bool operator==(const CWLSurface& other) const {\n            return other.resource() == resource();\n        }\n\n        bool operator==(const SP<CWLSurfaceResource> other) const {\n            return other == resource();\n        }\n\n        explicit operator bool() const {\n            return exists();\n        }\n\n        static SP<Desktop::View::CWLSurface> fromResource(SP<CWLSurfaceResource> pSurface);\n\n        // used by the alpha-modifier protocol\n        float m_alphaModifier = 1.F;\n\n        // used by the hyprland-surface protocol\n        float   m_overallOpacity = 1.F;\n        CRegion m_visibleRegion;\n\n        struct {\n            CSignalT<> destroy;\n        } m_events;\n\n        WP<Desktop::View::CWLSurface> m_self;\n\n      private:\n        CWLSurface() = default;\n\n        bool                   m_inert = true;\n\n        WP<CWLSurfaceResource> m_resource;\n\n        WP<IView>              m_view;\n\n        //\n        WP<CPointerConstraint> m_constraint;\n\n        void                   destroy();\n        void                   init();\n        bool                   desktopComponent() const;\n\n        struct {\n            CHyprSignalListener destroy;\n        } m_listeners;\n\n        friend class ::CPointerConstraint;\n    };\n}\n"
  },
  {
    "path": "src/desktop/view/Window.cpp",
    "content": "#include <algorithm>\n#include <ranges>\n#include <hyprutils/animation/AnimatedVariable.hpp>\n#include <re2/re2.h>\n\n#include \"Group.hpp\"\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#endif\n\n#include <any>\n#include <bit>\n#include <fstream>\n#include <string_view>\n#include \"Window.hpp\"\n#include \"LayerSurface.hpp\"\n#include \"../state/FocusState.hpp\"\n#include \"../history/WindowHistoryTracker.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../render/decorations/CHyprDropShadowDecoration.hpp\"\n#include \"../../render/decorations/CHyprGroupBarDecoration.hpp\"\n#include \"../../render/decorations/CHyprBorderDecoration.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../managers/TokenManager.hpp\"\n#include \"../../managers/animation/AnimationManager.hpp\"\n#include \"../../managers/ANRManager.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../protocols/XDGShell.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../protocols/core/Subcompositor.hpp\"\n#include \"../../protocols/ContentType.hpp\"\n#include \"../../protocols/FractionalScale.hpp\"\n#include \"../../protocols/LayerShell.hpp\"\n#include \"../../xwayland/XWayland.hpp\"\n#include \"../../helpers/Color.hpp\"\n#include \"../../helpers/math/Expression.hpp\"\n#include \"../../managers/XWaylandManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../managers/EventManager.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../managers/PointerManager.hpp\"\n#include \"../../managers/animation/DesktopAnimationManager.hpp\"\n#include \"../../layout/space/Space.hpp\"\n#include \"../../layout/LayoutManager.hpp\"\n#include \"../../layout/target/WindowTarget.hpp\"\n#include \"../../layout/target/WindowGroupTarget.hpp\"\n#include \"../../event/EventBus.hpp\"\n\n#include <hyprutils/string/String.hpp>\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Animation;\nusing enum NContentType::eContentType;\n\nusing namespace Desktop;\nusing namespace Desktop::View;\n\n// I wish I had an elven wife instead of a windowIDCounter\nstatic uint64_t windowIDCounter = 0x18000000;\n\n//\n#define COMMA ,\n//\n\nPHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {\n    PHLWINDOW pWindow = SP<CWindow>(new CWindow(surface));\n\n    pWindow->m_self           = pWindow;\n    pWindow->m_isX11          = true;\n    pWindow->m_ruleApplicator = makeUnique<Desktop::Rule::CWindowRuleApplicator>(pWindow);\n\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, g_pConfigManager->getAnimationPropertyConfig(\"windowsIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, g_pConfigManager->getAnimationPropertyConfig(\"windowsIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, g_pConfigManager->getAnimationPropertyConfig(\"border\"), pWindow, AVARDAMAGE_BORDER);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, g_pConfigManager->getAnimationPropertyConfig(\"borderangle\"), pWindow, AVARDAMAGE_BORDER);\n    g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeSwitch\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, g_pConfigManager->getAnimationPropertyConfig(\"fadeShadow\"), pWindow, AVARDAMAGE_SHADOW);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, g_pConfigManager->getAnimationPropertyConfig(\"fadeDim\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeOut\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, g_pConfigManager->getAnimationPropertyConfig(\"fade\"), pWindow, AVARDAMAGE_ENTIRE);\n\n    pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));\n    pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));\n\n    pWindow->m_target = Layout::CWindowTarget::create(pWindow);\n\n    return pWindow;\n}\n\nPHLWINDOW CWindow::create(SP<CXDGSurfaceResource> resource) {\n    PHLWINDOW pWindow = SP<CWindow>(new CWindow(resource));\n\n    pWindow->m_self                = pWindow;\n    resource->m_toplevel->m_window = pWindow;\n    pWindow->m_ruleApplicator      = makeUnique<Desktop::Rule::CWindowRuleApplicator>(pWindow);\n\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, g_pConfigManager->getAnimationPropertyConfig(\"windowsIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, g_pConfigManager->getAnimationPropertyConfig(\"windowsIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, g_pConfigManager->getAnimationPropertyConfig(\"border\"), pWindow, AVARDAMAGE_BORDER);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, g_pConfigManager->getAnimationPropertyConfig(\"borderangle\"), pWindow, AVARDAMAGE_BORDER);\n    g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeSwitch\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, g_pConfigManager->getAnimationPropertyConfig(\"fadeShadow\"), pWindow, AVARDAMAGE_SHADOW);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, g_pConfigManager->getAnimationPropertyConfig(\"fadeDim\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeOut\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"), pWindow, AVARDAMAGE_ENTIRE);\n    g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, g_pConfigManager->getAnimationPropertyConfig(\"fade\"), pWindow, AVARDAMAGE_ENTIRE);\n\n    pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));\n    pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));\n\n    pWindow->m_target = Layout::CWindowTarget::create(pWindow);\n\n    pWindow->wlSurface()->assign(pWindow->m_xdgSurface->m_surface.lock(), pWindow);\n\n    return pWindow;\n}\n\nCWindow::CWindow(SP<CXDGSurfaceResource> resource) : IView(CWLSurface::create()), m_xdgSurface(resource), m_stableID(windowIDCounter++) {\n    m_listeners.map            = m_xdgSurface->m_events.map.listen([this] { mapWindow(); });\n    m_listeners.ack            = m_xdgSurface->m_events.ack.listen([this](uint32_t d) { onAck(d); });\n    m_listeners.unmap          = m_xdgSurface->m_events.unmap.listen([this] { unmapWindow(); });\n    m_listeners.destroy        = m_xdgSurface->m_events.destroy.listen([this] { destroyWindow(); });\n    m_listeners.commit         = m_xdgSurface->m_events.commit.listen([this] { commitWindow(); });\n    m_listeners.updateState    = m_xdgSurface->m_toplevel->m_events.stateChanged.listen([this] { onUpdateState(); });\n    m_listeners.updateMetadata = m_xdgSurface->m_toplevel->m_events.metadataChanged.listen([this] { onUpdateMeta(); });\n}\n\nCWindow::CWindow(SP<CXWaylandSurface> surface) : IView(CWLSurface::create()), m_xwaylandSurface(surface), m_stableID(windowIDCounter++) {\n    m_listeners.map              = m_xwaylandSurface->m_events.map.listen([this] { mapWindow(); });\n    m_listeners.unmap            = m_xwaylandSurface->m_events.unmap.listen([this] { unmapWindow(); });\n    m_listeners.destroy          = m_xwaylandSurface->m_events.destroy.listen([this] { destroyWindow(); });\n    m_listeners.commit           = m_xwaylandSurface->m_events.commit.listen([this] { commitWindow(); });\n    m_listeners.configureRequest = m_xwaylandSurface->m_events.configureRequest.listen([this](const CBox& box) { onX11ConfigureRequest(box); });\n    m_listeners.updateState      = m_xwaylandSurface->m_events.stateChanged.listen([this] { onUpdateState(); });\n    m_listeners.updateMetadata   = m_xwaylandSurface->m_events.metadataChanged.listen([this] { onUpdateMeta(); });\n    m_listeners.resourceChange   = m_xwaylandSurface->m_events.resourceChange.listen([this] { onResourceChangeX11(); });\n    m_listeners.activate         = m_xwaylandSurface->m_events.activate.listen([this] { activateX11(); });\n\n    if (m_xwaylandSurface->m_overrideRedirect)\n        m_listeners.setGeometry = m_xwaylandSurface->m_events.setGeometry.listen([this] { unmanagedSetGeometry(); });\n}\n\nSP<CWindow> CWindow::fromView(SP<IView> v) {\n    if (!v || v->type() != VIEW_TYPE_WINDOW)\n        return nullptr;\n    return dynamicPointerCast<CWindow>(v);\n}\n\nCWindow::~CWindow() {\n    if (Desktop::focusState()->window() == m_self) {\n        Desktop::focusState()->surface().reset();\n        Desktop::focusState()->window().reset();\n    }\n\n    m_events.destroy.emit();\n}\n\neViewType CWindow::type() const {\n    return VIEW_TYPE_WINDOW;\n}\n\nbool CWindow::visible() const {\n    return !m_hidden && ((m_isMapped && m_wlSurface && m_wlSurface->resource()) || (m_fadingOut && m_alpha->value() != 0.F));\n}\n\nstd::optional<CBox> CWindow::logicalBox() const {\n    return getFullWindowBoundingBox();\n}\n\nbool CWindow::desktopComponent() const {\n    return true;\n}\n\nstd::optional<CBox> CWindow::surfaceLogicalBox() const {\n    return getWindowMainSurfaceBox();\n}\n\nSBoxExtents CWindow::getFullWindowExtents() const {\n    if (m_fadingOut)\n        return m_originalClosedExtents;\n\n    const int BORDERSIZE = getRealBorderSize();\n\n    if (m_ruleApplicator->dimAround().valueOrDefault()) {\n        if (const auto PMONITOR = m_monitor.lock(); PMONITOR)\n            return {.topLeft     = {m_realPosition->value().x - PMONITOR->m_position.x, m_realPosition->value().y - PMONITOR->m_position.y},\n                    .bottomRight = {PMONITOR->m_size.x - (m_realPosition->value().x - PMONITOR->m_position.x),\n                                    PMONITOR->m_size.y - (m_realPosition->value().y - PMONITOR->m_position.y)}};\n    }\n\n    SBoxExtents maxExtents = {.topLeft = {BORDERSIZE + 2, BORDERSIZE + 2}, .bottomRight = {BORDERSIZE + 2, BORDERSIZE + 2}};\n\n    const auto  EXTENTS = g_pDecorationPositioner->getWindowDecorationExtents(m_self);\n\n    maxExtents.topLeft.x = std::max(EXTENTS.topLeft.x, maxExtents.topLeft.x);\n\n    maxExtents.topLeft.y = std::max(EXTENTS.topLeft.y, maxExtents.topLeft.y);\n\n    maxExtents.bottomRight.x = std::max(EXTENTS.bottomRight.x, maxExtents.bottomRight.x);\n\n    maxExtents.bottomRight.y = std::max(EXTENTS.bottomRight.y, maxExtents.bottomRight.y);\n\n    if (m_wlSurface->exists() && !m_isX11 && m_popupHead) {\n        CBox surfaceExtents = {0, 0, 0, 0};\n        // TODO: this could be better, perhaps make a getFullWindowRegion?\n        m_popupHead->breadthfirst(\n            [](WP<Desktop::View::CPopup> popup, void* data) {\n                if (!popup->wlSurface() || !popup->wlSurface()->resource())\n                    return;\n\n                CBox* pSurfaceExtents = sc<CBox*>(data);\n                CBox  surf            = CBox{popup->coordsRelativeToParent(), popup->size()};\n                pSurfaceExtents->x    = std::min(surf.x, pSurfaceExtents->x);\n                pSurfaceExtents->y    = std::min(surf.y, pSurfaceExtents->y);\n                if (surf.x + surf.w > pSurfaceExtents->width)\n                    pSurfaceExtents->width = surf.x + surf.w - pSurfaceExtents->x;\n                if (surf.y + surf.h > pSurfaceExtents->height)\n                    pSurfaceExtents->height = surf.y + surf.h - pSurfaceExtents->y;\n            },\n            &surfaceExtents);\n\n        maxExtents.topLeft.x = std::max(-surfaceExtents.x, maxExtents.topLeft.x);\n\n        maxExtents.topLeft.y = std::max(-surfaceExtents.y, maxExtents.topLeft.y);\n\n        if (surfaceExtents.x + surfaceExtents.width > m_wlSurface->resource()->m_current.size.x + maxExtents.bottomRight.x)\n            maxExtents.bottomRight.x = surfaceExtents.x + surfaceExtents.width - m_wlSurface->resource()->m_current.size.x;\n\n        if (surfaceExtents.y + surfaceExtents.height > m_wlSurface->resource()->m_current.size.y + maxExtents.bottomRight.y)\n            maxExtents.bottomRight.y = surfaceExtents.y + surfaceExtents.height - m_wlSurface->resource()->m_current.size.y;\n    }\n\n    return maxExtents;\n}\n\nCBox CWindow::getFullWindowBoundingBox() const {\n    if (m_ruleApplicator->dimAround().valueOrDefault()) {\n        if (const auto PMONITOR = m_monitor.lock(); PMONITOR)\n            return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y};\n    }\n\n    auto maxExtents = getFullWindowExtents();\n\n    CBox finalBox = {m_realPosition->value().x - maxExtents.topLeft.x, m_realPosition->value().y - maxExtents.topLeft.y,\n                     m_realSize->value().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_realSize->value().y + maxExtents.topLeft.y + maxExtents.bottomRight.y};\n\n    return finalBox;\n}\n\nCBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {\n    const auto PMONITOR = m_monitor.lock();\n\n    if (!PMONITOR || !m_workspace)\n        return {m_position, m_size};\n\n    auto POS  = m_position;\n    auto SIZE = m_size;\n\n    if (isFullscreen()) {\n        POS  = PMONITOR->m_position;\n        SIZE = PMONITOR->m_size;\n\n        return CBox{sc<int>(POS.x), sc<int>(POS.y), sc<int>(SIZE.x), sc<int>(SIZE.y)};\n    }\n\n    // fucker fucking fuck\n    const auto  WORKAREA = m_workspace->m_space->workArea();\n    const auto& RESERVED = CReservedArea(PMONITOR->logicalBox(), WORKAREA);\n\n    if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) {\n        POS.x -= RESERVED.left();\n        SIZE.x += RESERVED.left();\n    }\n\n    if (DELTALESSTHAN(POS.y, WORKAREA.y, 1)) {\n        POS.y -= RESERVED.top();\n        SIZE.y += RESERVED.top();\n    }\n\n    if (DELTALESSTHAN(POS.x + SIZE.x, WORKAREA.x + WORKAREA.width, 1))\n        SIZE.x += RESERVED.right();\n\n    if (DELTALESSTHAN(POS.y + SIZE.y, WORKAREA.y + WORKAREA.height, 1))\n        SIZE.y += RESERVED.bottom();\n\n    return CBox{sc<int>(POS.x), sc<int>(POS.y), sc<int>(SIZE.x), sc<int>(SIZE.y)};\n}\n\nSBoxExtents CWindow::getWindowExtentsUnified(uint64_t properties) {\n    SBoxExtents extents = {.topLeft = {0, 0}, .bottomRight = {0, 0}};\n    if (properties & Desktop::View::RESERVED_EXTENTS)\n        extents.addExtents(g_pDecorationPositioner->getWindowDecorationReserved(m_self));\n    if (properties & Desktop::View::INPUT_EXTENTS)\n        extents.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(m_self, true));\n    if (properties & FULL_EXTENTS)\n        extents.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(m_self, false));\n\n    return extents;\n}\n\nCBox CWindow::getWindowBoxUnified(uint64_t properties) {\n    if (m_ruleApplicator->dimAround().valueOrDefault()) {\n        const auto PMONITOR = m_monitor.lock();\n        if (PMONITOR)\n            return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y};\n    }\n\n    const auto POS  = m_realPosition->value();\n    const auto SIZE = m_realSize->value();\n\n    CBox       box{POS, SIZE};\n    box.addExtents(getWindowExtentsUnified(properties));\n\n    return box;\n}\n\nSBoxExtents CWindow::getFullWindowReservedArea() {\n    return g_pDecorationPositioner->getWindowDecorationReserved(m_self);\n}\n\nvoid CWindow::updateWindowDecos() {\n\n    if (!m_isMapped || isHidden())\n        return;\n\n    for (auto const& wd : m_decosToRemove) {\n        for (auto it = m_windowDecorations.begin(); it != m_windowDecorations.end(); it++) {\n            if (it->get() == wd) {\n                g_pDecorationPositioner->uncacheDecoration(it->get());\n                it = m_windowDecorations.erase(it);\n                if (it == m_windowDecorations.end())\n                    break;\n            }\n        }\n    }\n\n    g_pDecorationPositioner->onWindowUpdate(m_self.lock());\n\n    m_decosToRemove.clear();\n\n    // make a copy because updateWindow can remove decos.\n    std::vector<IHyprWindowDecoration*> decos;\n    // reserve to avoid reallocations\n    decos.reserve(m_windowDecorations.size());\n\n    for (auto const& wd : m_windowDecorations) {\n        decos.push_back(wd.get());\n    }\n\n    for (auto const& wd : decos) {\n        if (std::ranges::find_if(m_windowDecorations, [wd](const auto& other) { return other.get() == wd; }) == m_windowDecorations.end())\n            continue;\n        wd->updateWindow(m_self.lock());\n    }\n}\n\nvoid CWindow::addWindowDeco(UP<IHyprWindowDecoration> deco) {\n    m_windowDecorations.emplace_back(std::move(deco));\n    g_pDecorationPositioner->forceRecalcFor(m_self.lock());\n    updateWindowDecos();\n\n    if (layoutTarget())\n        layoutTarget()->recalc();\n}\n\nvoid CWindow::removeWindowDeco(IHyprWindowDecoration* deco) {\n    m_decosToRemove.push_back(deco);\n    g_pDecorationPositioner->forceRecalcFor(m_self.lock());\n    updateWindowDecos();\n\n    if (layoutTarget())\n        layoutTarget()->recalc();\n}\n\nvoid CWindow::uncacheWindowDecos() {\n    for (auto const& wd : m_windowDecorations) {\n        g_pDecorationPositioner->uncacheDecoration(wd.get());\n    }\n}\n\nbool CWindow::checkInputOnDecos(const eInputType type, const Vector2D& mouseCoords, std::any data) {\n    if (type != INPUT_TYPE_DRAG_END && hasPopupAt(mouseCoords))\n        return false;\n\n    for (auto const& wd : m_windowDecorations) {\n        if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT))\n            continue;\n\n        if (!g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords))\n            continue;\n\n        if (wd->onInputOnDeco(type, mouseCoords, data))\n            return true;\n    }\n\n    return false;\n}\n\npid_t CWindow::getPID() {\n    pid_t PID = -1;\n    if (!m_isX11) {\n        if (!m_xdgSurface || !m_xdgSurface->m_owner /* happens at unmap */)\n            return -1;\n\n        wl_client_get_credentials(m_xdgSurface->m_owner->client(), &PID, nullptr, nullptr);\n    } else {\n        if (!m_xwaylandSurface)\n            return -1;\n\n        PID = m_xwaylandSurface->m_pid;\n    }\n\n    return PID;\n}\n\nIHyprWindowDecoration* CWindow::getDecorationByType(eDecorationType type) {\n    for (auto const& wd : m_windowDecorations) {\n        if (wd->getDecorationType() == type)\n            return wd.get();\n    }\n\n    return nullptr;\n}\n\nvoid CWindow::updateToplevel() {\n    updateSurfaceScaleTransformDetails();\n}\n\nvoid CWindow::updateSurfaceScaleTransformDetails(bool force) {\n    if (!m_isMapped || m_hidden || g_pCompositor->m_unsafeState)\n        return;\n\n    const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_lastSurfaceMonitorID);\n\n    m_lastSurfaceMonitorID = monitorID();\n\n    const auto PNEWMONITOR = m_monitor.lock();\n\n    if (!PNEWMONITOR)\n        return;\n\n    if (PNEWMONITOR != PLASTMONITOR || force) {\n        if (PLASTMONITOR && PLASTMONITOR->m_enabled && PNEWMONITOR != PLASTMONITOR)\n            m_wlSurface->resource()->breadthfirst([PLASTMONITOR](SP<CWLSurfaceResource> s, const Vector2D& offset, void* d) { s->leave(PLASTMONITOR->m_self.lock()); }, nullptr);\n\n        m_wlSurface->resource()->breadthfirst([PNEWMONITOR](SP<CWLSurfaceResource> s, const Vector2D& offset, void* d) { s->enter(PNEWMONITOR->m_self.lock()); }, nullptr);\n    }\n\n    const auto PMONITOR = m_monitor.lock();\n\n    m_wlSurface->resource()->breadthfirst(\n        [PMONITOR](SP<CWLSurfaceResource> s, const Vector2D& offset, void* d) {\n            const auto PSURFACE = CWLSurface::fromResource(s);\n            if (PSURFACE && PSURFACE->m_lastScaleFloat == PMONITOR->m_scale)\n                return;\n\n            PROTO::fractional->sendScale(s, PMONITOR->m_scale);\n            g_pCompositor->setPreferredScaleForSurface(s, PMONITOR->m_scale);\n            g_pCompositor->setPreferredTransformForSurface(s, PMONITOR->m_transform);\n        },\n        nullptr);\n}\n\nvoid CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {\n    if (m_workspace == pWorkspace)\n        return;\n\n    static auto PINITIALWSTRACKING = CConfigValue<Hyprlang::INT>(\"misc:initial_workspace_tracking\");\n\n    if (!m_initialWorkspaceToken.empty()) {\n        const auto TOKEN = g_pTokenManager->getToken(m_initialWorkspaceToken);\n        if (TOKEN) {\n            if (*PINITIALWSTRACKING == 2) {\n                // persistent\n                try {\n                    SInitialWorkspaceToken token = std::any_cast<SInitialWorkspaceToken>(TOKEN->m_data);\n                    if (token.primaryOwner == m_self) {\n                        token.workspace = pWorkspace->getConfigName();\n                        TOKEN->m_data   = token;\n                    }\n                } catch (const std::bad_any_cast& e) { ; }\n            }\n        }\n    }\n\n    static auto PCLOSEONLASTSPECIAL = CConfigValue<Hyprlang::INT>(\"misc:close_special_on_empty\");\n\n    const auto  OLDWORKSPACE = m_workspace;\n\n    if (OLDWORKSPACE->isVisible()) {\n        m_movingToWorkspaceAlpha->setValueAndWarp(1.F);\n        *m_movingToWorkspaceAlpha = 0.F;\n        m_movingToWorkspaceAlpha->setCallbackOnEnd([this](auto) { m_monitorMovedFrom = -1; });\n        m_monitorMovedFrom = OLDWORKSPACE ? OLDWORKSPACE->monitorID() : -1;\n    }\n\n    m_workspace = pWorkspace;\n\n    setAnimationsToMove();\n\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    if (valid(pWorkspace)) {\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"movewindow\", .data = std::format(\"{:x},{}\", rc<uintptr_t>(this), pWorkspace->m_name)});\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"movewindowv2\", .data = std::format(\"{:x},{},{}\", rc<uintptr_t>(this), pWorkspace->m_id, pWorkspace->m_name)});\n        Event::bus()->m_events.window.moveToWorkspace.emit(m_self.lock(), pWorkspace);\n    }\n\n    if (const auto SWALLOWED = m_swallowed.lock()) {\n        if (SWALLOWED->m_currentlySwallowed) {\n            SWALLOWED->moveToWorkspace(pWorkspace);\n            SWALLOWED->m_monitor = m_monitor;\n        }\n    }\n\n    if (OLDWORKSPACE && g_pCompositor->isWorkspaceSpecial(OLDWORKSPACE->m_id) && OLDWORKSPACE->getWindows() == 0 && *PCLOSEONLASTSPECIAL) {\n        if (const auto PMONITOR = OLDWORKSPACE->m_monitor.lock(); PMONITOR)\n            PMONITOR->setSpecialWorkspace(nullptr);\n    }\n}\n\nPHLWINDOW CWindow::x11TransientFor() {\n    if (!m_xwaylandSurface || !m_xwaylandSurface->m_parent)\n        return nullptr;\n\n    auto                              s = m_xwaylandSurface->m_parent;\n    std::vector<SP<CXWaylandSurface>> visited;\n    while (s) {\n        // break loops. Some X apps make them, and it seems like it's valid behavior?!?!?!\n        // TODO: we should reject loops being created in the first place.\n        if (std::ranges::find(visited.begin(), visited.end(), s) != visited.end())\n            break;\n\n        visited.emplace_back(s.lock());\n        s = s->m_parent;\n    }\n\n    if (s == m_xwaylandSurface)\n        return nullptr; // dead-ass circle\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_xwaylandSurface != s)\n            continue;\n        return w;\n    }\n\n    return nullptr;\n}\n\nvoid CWindow::onUnmap() {\n    static auto PCLOSEONLASTSPECIAL = CConfigValue<Hyprlang::INT>(\"misc:close_special_on_empty\");\n    static auto PINITIALWSTRACKING  = CConfigValue<Hyprlang::INT>(\"misc:initial_workspace_tracking\");\n\n    if (!m_initialWorkspaceToken.empty()) {\n        const auto TOKEN = g_pTokenManager->getToken(m_initialWorkspaceToken);\n        if (TOKEN) {\n            if (*PINITIALWSTRACKING == 2) {\n                // persistent token, but the first window got removed so the token is gone\n                try {\n                    SInitialWorkspaceToken token = std::any_cast<SInitialWorkspaceToken>(TOKEN->m_data);\n                    if (token.primaryOwner == m_self)\n                        g_pTokenManager->removeToken(TOKEN);\n                } catch (const std::bad_any_cast& e) { g_pTokenManager->removeToken(TOKEN); }\n            }\n        }\n    }\n\n    m_lastWorkspace = m_workspace->m_id;\n\n    // if the special workspace now has 0 windows, it will be closed, and this\n    // window will no longer pass render checks, cuz the workspace will be nuked.\n    // throw it into the main one for the fadeout.\n    if (m_workspace->m_isSpecialWorkspace && m_workspace->getWindows() == 0)\n        m_lastWorkspace = m_monitor->activeWorkspaceID();\n\n    if (*PCLOSEONLASTSPECIAL && m_workspace && m_workspace->getWindows() == 0 && onSpecialWorkspace()) {\n        const auto PMONITOR = m_monitor.lock();\n        if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace == m_workspace)\n            PMONITOR->setSpecialWorkspace(nullptr);\n    }\n\n    const auto PMONITOR = m_monitor.lock();\n\n    if (PMONITOR && PMONITOR->m_solitaryClient == m_self)\n        PMONITOR->m_solitaryClient.reset();\n\n    if (m_workspace) {\n        m_workspace->updateWindows();\n        m_workspace->updateWindowData();\n    }\n\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    m_workspace.reset();\n\n    if (m_isX11)\n        return;\n\n    m_subsurfaceHead.reset();\n    m_popupHead.reset();\n}\n\nvoid CWindow::onMap() {\n    // JIC, reset the callbacks. If any are set, we'll make sure they are cleared so we don't accidentally unset them. (In case a window got remapped)\n    m_realPosition->resetAllCallbacks();\n    m_realSize->resetAllCallbacks();\n    m_borderFadeAnimationProgress->resetAllCallbacks();\n    m_borderAngleAnimationProgress->resetAllCallbacks();\n    m_activeInactiveAlpha->resetAllCallbacks();\n    m_alpha->resetAllCallbacks();\n    m_realShadowColor->resetAllCallbacks();\n    m_dimPercent->resetAllCallbacks();\n    m_movingToWorkspaceAlpha->resetAllCallbacks();\n    m_movingFromWorkspaceAlpha->resetAllCallbacks();\n\n    m_movingFromWorkspaceAlpha->setValueAndWarp(1.F);\n\n    if (m_borderAngleAnimationProgress->enabled()) {\n        m_borderAngleAnimationProgress->setValueAndWarp(0.f);\n        m_borderAngleAnimationProgress->setCallbackOnEnd([&](WP<CBaseAnimatedVariable> p) { onBorderAngleAnimEnd(p); }, false);\n        *m_borderAngleAnimationProgress = 1.f;\n    }\n\n    m_realSize->setCallbackOnBegin(\n        [this](auto) {\n            if (!m_isMapped || isX11OverrideRedirect())\n                return;\n\n            g_pEventLoopManager->doLater([this, self = m_self] {\n                if (!self)\n                    return;\n\n                sendWindowSize();\n            });\n        },\n        false);\n\n    m_realSize->setUpdateCallback([this](auto) {\n        if (m_isMapped)\n            m_events.resize.emit();\n    });\n\n    m_realPosition->setUpdateCallback([this](auto) {\n        if (m_isMapped && m_monitor != m_prevMonitor) {\n            m_prevMonitor = m_monitor;\n            m_events.monitorChanged.emit();\n        }\n    });\n\n    m_movingFromWorkspaceAlpha->setValueAndWarp(1.F);\n\n    m_reportedSize = m_pendingReportedSize;\n    m_animatingIn  = true;\n\n    updateSurfaceScaleTransformDetails(true);\n\n    if (m_isX11)\n        return;\n\n    m_subsurfaceHead = CSubsurface::create(m_self.lock());\n    m_popupHead      = CPopup::create(m_self.lock());\n}\n\nvoid CWindow::onBorderAngleAnimEnd(WP<CBaseAnimatedVariable> pav) {\n    if (!pav)\n        return;\n\n    if (pav->getStyle() != \"loop\" || !pav->enabled())\n        return;\n\n    const auto PANIMVAR = dc<CAnimatedVariable<float>*>(pav.get());\n\n    PANIMVAR->setCallbackOnEnd(nullptr); // we remove the callback here because otherwise setvalueandwarp will recurse this\n\n    PANIMVAR->setValueAndWarp(0);\n    *PANIMVAR = 1.f;\n\n    PANIMVAR->setCallbackOnEnd([&](WP<CBaseAnimatedVariable> pav) { onBorderAngleAnimEnd(pav); }, false);\n}\n\nvoid CWindow::setHidden(bool hidden) {\n    m_hidden = hidden;\n\n    if (hidden)\n        m_events.hide.emit();\n\n    if (hidden && Desktop::focusState()->window() == m_self)\n        Desktop::focusState()->window().reset();\n\n    setSuspended(hidden);\n}\n\nbool CWindow::isHidden() {\n    return m_hidden;\n}\n\n// check if the point is \"hidden\" under a rounded corner of the window\n// it is assumed that the point is within the real window box (m_vRealPosition, m_vRealSize)\n// otherwise behaviour is undefined\nbool CWindow::isInCurvedCorner(double x, double y) {\n    const int ROUNDING      = rounding();\n    const int ROUNDINGPOWER = roundingPower();\n    if (getRealBorderSize() >= ROUNDING)\n        return false;\n\n    // (x0, y0), (x0, y1), ... are the center point of rounding at each corner\n    double x0 = m_realPosition->value().x + ROUNDING;\n    double y0 = m_realPosition->value().y + ROUNDING;\n    double x1 = m_realPosition->value().x + m_realSize->value().x - ROUNDING;\n    double y1 = m_realPosition->value().y + m_realSize->value().y - ROUNDING;\n\n    if (x < x0 && y < y0) {\n        return std::pow(x0 - x, ROUNDINGPOWER) + std::pow(y0 - y, ROUNDINGPOWER) > std::pow(sc<double>(ROUNDING), ROUNDINGPOWER);\n    }\n    if (x > x1 && y < y0) {\n        return std::pow(x - x1, ROUNDINGPOWER) + std::pow(y0 - y, ROUNDINGPOWER) > std::pow(sc<double>(ROUNDING), ROUNDINGPOWER);\n    }\n    if (x < x0 && y > y1) {\n        return std::pow(x0 - x, ROUNDINGPOWER) + std::pow(y - y1, ROUNDINGPOWER) > std::pow(sc<double>(ROUNDING), ROUNDINGPOWER);\n    }\n    if (x > x1 && y > y1) {\n        return std::pow(x - x1, ROUNDINGPOWER) + std::pow(y - y1, ROUNDINGPOWER) > std::pow(sc<double>(ROUNDING), ROUNDINGPOWER);\n    }\n\n    return false;\n}\n\n// checks if the wayland window has a popup at pos\nbool CWindow::hasPopupAt(const Vector2D& pos) {\n    if (m_isX11)\n        return false;\n\n    auto popup = m_popupHead->at(pos);\n\n    return popup && popup->wlSurface()->resource();\n}\n\nVector2D CWindow::middle() {\n    return m_realPosition->goal() + m_realSize->goal() / 2.f;\n}\n\nbool CWindow::opaque() {\n    if (m_alpha->value() != 1.f || m_activeInactiveAlpha->value() != 1.f)\n        return false;\n\n    const auto PWORKSPACE = m_workspace;\n\n    if (m_wlSurface->small() && !m_wlSurface->m_fillIgnoreSmall)\n        return false;\n\n    if (PWORKSPACE && PWORKSPACE->m_alpha->value() != 1.f)\n        return false;\n\n    if (m_isX11 && m_xwaylandSurface && m_xwaylandSurface->m_surface && m_xwaylandSurface->m_surface->m_current.texture)\n        return m_xwaylandSurface->m_surface->m_current.texture->m_opaque;\n\n    auto solitaryResource = getSolitaryResource();\n    if (!solitaryResource || !solitaryResource->m_current.texture)\n        return false;\n\n    // TODO: this is wrong\n    const auto EXTENTS = m_xdgSurface->m_surface->m_current.opaque.getExtents();\n    if (EXTENTS.w >= m_xdgSurface->m_surface->m_current.bufferSize.x && EXTENTS.h >= m_xdgSurface->m_surface->m_current.bufferSize.y)\n        return true;\n\n    return solitaryResource->m_current.texture->m_opaque;\n}\n\nfloat CWindow::rounding() {\n    static auto PROUNDING      = CConfigValue<Hyprlang::INT>(\"decoration:rounding\");\n    static auto PROUNDINGPOWER = CConfigValue<Hyprlang::FLOAT>(\"decoration:rounding_power\");\n\n    float       roundingPower = m_ruleApplicator->roundingPower().valueOr(*PROUNDINGPOWER);\n    float       rounding      = m_ruleApplicator->rounding().valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */\n\n    return rounding;\n}\n\nfloat CWindow::roundingPower() {\n    static auto PROUNDINGPOWER = CConfigValue<Hyprlang::FLOAT>(\"decoration:rounding_power\");\n\n    return m_ruleApplicator->roundingPower().valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F));\n}\n\nvoid CWindow::updateWindowData() {\n    const auto PWORKSPACE    = m_workspace;\n    const auto WORKSPACERULE = PWORKSPACE ? g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE) : SWorkspaceRule{};\n    updateWindowData(WORKSPACERULE);\n}\n\nvoid CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) {\n    if (workspaceRule.noBorder.value_or(false))\n        m_ruleApplicator->borderSize().matchOptional(std::optional<Hyprlang::INT>(0), Desktop::Types::PRIORITY_WORKSPACE_RULE);\n    else if (workspaceRule.borderSize)\n        m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE);\n    else\n        m_ruleApplicator->borderSize().matchOptional(std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE);\n    m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE);\n    m_ruleApplicator->rounding().matchOptional(workspaceRule.noRounding.value_or(false) ? std::optional<Hyprlang::INT>(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE);\n    m_ruleApplicator->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE);\n}\n\nint CWindow::getRealBorderSize() const {\n    if ((m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_ruleApplicator->decorate().valueOrDefault())\n        return 0;\n\n    static auto PBORDERSIZE = CConfigValue<Hyprlang::INT>(\"general:border_size\");\n\n    return m_ruleApplicator->borderSize().valueOr(*PBORDERSIZE);\n}\n\nfloat CWindow::getScrollMouse() {\n    static auto PINPUTSCROLLFACTOR = CConfigValue<Hyprlang::FLOAT>(\"input:scroll_factor\");\n    return m_ruleApplicator->scrollMouse().valueOr(*PINPUTSCROLLFACTOR);\n}\n\nfloat CWindow::getScrollTouchpad() {\n    static auto PTOUCHPADSCROLLFACTOR = CConfigValue<Hyprlang::FLOAT>(\"input:touchpad:scroll_factor\");\n    return m_ruleApplicator->scrollTouchpad().valueOr(*PTOUCHPADSCROLLFACTOR);\n}\n\nbool CWindow::isScrollMouseOverridden() {\n    return m_ruleApplicator->scrollMouse().hasValue();\n}\n\nbool CWindow::isScrollTouchpadOverridden() {\n    return m_ruleApplicator->scrollTouchpad().hasValue();\n}\n\nbool CWindow::canBeTorn() {\n    static auto PTEARING = CConfigValue<Hyprlang::INT>(\"general:allow_tearing\");\n    return m_ruleApplicator->tearing().valueOr(m_tearingHint) && *PTEARING;\n}\n\nvoid CWindow::setSuspended(bool suspend) {\n    if (suspend == m_suspended)\n        return;\n\n    if (m_isX11 || !m_xdgSurface || !m_xdgSurface->m_toplevel)\n        return;\n\n    m_xdgSurface->m_toplevel->setSuspeneded(suspend);\n    m_suspended = suspend;\n}\n\nbool CWindow::visibleOnMonitor(PHLMONITOR pMonitor) {\n    CBox wbox = {m_realPosition->value(), m_realSize->value()};\n\n    if (m_isFloating)\n        wbox = getFullWindowBoundingBox();\n\n    return !wbox.intersection({pMonitor->m_position, pMonitor->m_size}).empty();\n}\n\nvoid CWindow::setAnimationsToMove() {\n    m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"windowsMove\"));\n    m_animatingIn = false;\n}\n\nvoid CWindow::onWorkspaceAnimUpdate() {\n    // clip box for animated offsets\n    if (!m_isFloating || m_pinned || isFullscreen()) {\n        m_floatingOffset = Vector2D(0, 0);\n        return;\n    }\n\n    Vector2D   offset;\n    const auto PWORKSPACE = m_workspace;\n    if (!PWORKSPACE)\n        return;\n\n    const auto PWSMON = m_monitor.lock();\n    if (!PWSMON)\n        return;\n\n    const auto WINBB = getFullWindowBoundingBox();\n    if (PWORKSPACE->m_renderOffset->value().x != 0) {\n        const auto PROGRESS = PWORKSPACE->m_renderOffset->value().x / PWSMON->m_size.x;\n\n        if (WINBB.x < PWSMON->m_position.x)\n            offset.x += (PWSMON->m_position.x - WINBB.x) * PROGRESS;\n\n        if (WINBB.x + WINBB.width > PWSMON->m_position.x + PWSMON->m_size.x)\n            offset.x += (WINBB.x + WINBB.width - PWSMON->m_position.x - PWSMON->m_size.x) * PROGRESS;\n    } else if (PWORKSPACE->m_renderOffset->value().y != 0) {\n        const auto PROGRESS = PWORKSPACE->m_renderOffset->value().y / PWSMON->m_size.y;\n\n        if (WINBB.y < PWSMON->m_position.y)\n            offset.y += (PWSMON->m_position.y - WINBB.y) * PROGRESS;\n\n        if (WINBB.y + WINBB.height > PWSMON->m_position.y + PWSMON->m_size.y)\n            offset.y += (WINBB.y + WINBB.height - PWSMON->m_position.y - PWSMON->m_size.y) * PROGRESS;\n    }\n\n    m_floatingOffset = offset;\n}\n\nvoid CWindow::onFocusAnimUpdate() {\n    // borderangle once\n    if (m_borderAngleAnimationProgress->enabled() && !m_borderAngleAnimationProgress->isBeingAnimated()) {\n        m_borderAngleAnimationProgress->setValueAndWarp(0.f);\n        *m_borderAngleAnimationProgress = 1.f;\n    }\n}\n\nint CWindow::popupsCount() {\n    if (m_isX11 || !m_popupHead)\n        return 0;\n\n    int no = -1;\n    m_popupHead->breadthfirst([](WP<Desktop::View::CPopup> p, void* d) { *sc<int*>(d) += 1; }, &no);\n    return no;\n}\n\nint CWindow::surfacesCount() {\n    if (m_isX11)\n        return 1;\n\n    int no = 0;\n    m_wlSurface->resource()->breadthfirst([](SP<CWLSurfaceResource> r, const Vector2D& offset, void* d) { *sc<int*>(d) += 1; }, &no);\n    return no;\n}\n\nbool CWindow::clampWindowSize(const std::optional<Vector2D> minSize, const std::optional<Vector2D> maxSize) {\n    const Vector2D REALSIZE = m_realSize->goal();\n    const Vector2D MAX      = isFullscreen() ? Vector2D{INFINITY, INFINITY} : maxSize.value_or(Vector2D{INFINITY, INFINITY});\n    const Vector2D NEWSIZE  = REALSIZE.clamp(minSize.value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), MAX);\n    const bool     changed  = !(NEWSIZE == REALSIZE);\n\n    if (changed) {\n        const Vector2D DELTA = REALSIZE - NEWSIZE;\n        *m_realPosition      = m_realPosition->goal() + DELTA / 2.0;\n        *m_realSize          = NEWSIZE;\n    }\n\n    return changed;\n}\n\nbool CWindow::isFullscreen() {\n    return m_fullscreenState.internal != FSMODE_NONE;\n}\n\nbool CWindow::isEffectiveInternalFSMode(const eFullscreenMode MODE) const {\n    return sc<eFullscreenMode>(std::bit_floor(sc<uint8_t>(m_fullscreenState.internal))) == MODE;\n}\n\nWORKSPACEID CWindow::workspaceID() {\n    return m_workspace ? m_workspace->m_id : m_lastWorkspace;\n}\n\nMONITORID CWindow::monitorID() {\n    return m_monitor ? m_monitor->m_id : MONITOR_INVALID;\n}\n\nbool CWindow::onSpecialWorkspace() {\n    return m_workspace ? m_workspace->m_isSpecialWorkspace : g_pCompositor->isWorkspaceSpecial(m_lastWorkspace);\n}\n\nstd::unordered_map<std::string, std::string> CWindow::getEnv() {\n\n    const auto PID = getPID();\n\n    if (PID <= 1)\n        return {};\n\n    std::unordered_map<std::string, std::string> results;\n\n    std::vector<char>                            buffer;\n    size_t                                       needle = 0;\n\n#if defined(__linux__)\n    //\n    std::string   environFile = \"/proc/\" + std::to_string(PID) + \"/environ\";\n    std::ifstream ifs(environFile, std::ios::binary);\n\n    if (!ifs.good())\n        return {};\n\n    buffer.resize(512, '\\0');\n    while (ifs.read(buffer.data() + needle, 512)) {\n        buffer.resize(buffer.size() + 512, '\\0');\n        needle += 512;\n    }\n#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)\n    int    mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ENV, static_cast<int>(PID)};\n    size_t len    = 0;\n\n    if (sysctl(mib, 4, nullptr, &len, nullptr, 0) < 0 || len == 0)\n        return {};\n\n    buffer.resize(len, '\\0');\n\n    if (sysctl(mib, 4, buffer.data(), &len, nullptr, 0) < 0)\n        return {};\n\n    needle = len;\n#endif\n\n    if (needle <= 1)\n        return {};\n\n    std::replace(buffer.begin(), buffer.end() - 1, '\\0', '\\n');\n\n    CVarList envs(std::string{buffer.data(), buffer.size() - 1}, 0, '\\n', true);\n\n    for (auto const& e : envs) {\n        if (!e.contains('='))\n            continue;\n\n        const auto EQ            = e.find_first_of('=');\n        results[e.substr(0, EQ)] = e.substr(EQ + 1);\n    }\n\n    return results;\n}\n\nvoid CWindow::activate(bool force) {\n    if (Desktop::focusState()->window() == m_self)\n        return;\n\n    static auto PFOCUSONACTIVATE = CConfigValue<Hyprlang::INT>(\"misc:focus_on_activate\");\n\n    m_isUrgent = true;\n\n    g_pEventManager->postEvent(SHyprIPCEvent{.event = \"urgent\", .data = std::format(\"{:x}\", rc<uintptr_t>(this))});\n    Event::bus()->m_events.window.urgent.emit(m_self.lock());\n\n    if (!force &&\n        (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE)))\n        return;\n\n    if (!m_isMapped) {\n        Log::logger->log(Log::DEBUG, \"Ignoring CWindow::activate focus/warp, window is not mapped yet.\");\n        return;\n    }\n\n    if (m_isFloating)\n        g_pCompositor->changeWindowZOrder(m_self.lock(), true);\n\n    Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);\n    warpCursor();\n}\n\nvoid CWindow::onUpdateState() {\n    std::optional<bool>      requestsFS = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsFullscreen : m_xwaylandSurface->m_state.requestsFullscreen;\n    std::optional<MONITORID> requestsID = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsFullscreenMonitor : MONITOR_INVALID;\n    std::optional<bool>      requestsMX = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsMaximize : m_xwaylandSurface->m_state.requestsMaximize;\n\n    if (requestsFS.has_value() && !(m_suppressedEvents & SUPPRESS_FULLSCREEN)) {\n        if (requestsID.has_value() && (requestsID.value() != MONITOR_INVALID) && !(m_suppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT)) {\n            if (m_isMapped) {\n                const auto monitor = g_pCompositor->getMonitorFromID(requestsID.value());\n                g_pCompositor->moveWindowToWorkspaceSafe(m_self.lock(), monitor->m_activeWorkspace);\n                Desktop::focusState()->rawMonitorFocus(monitor);\n            }\n\n            if (!m_isMapped)\n                m_wantsInitialFullscreenMonitor = requestsID.value();\n        }\n\n        bool fs = requestsFS.value();\n        if (m_isMapped)\n            g_pCompositor->changeWindowFullscreenModeClient(m_self.lock(), FSMODE_FULLSCREEN, requestsFS.value());\n\n        if (!m_isMapped)\n            m_wantsInitialFullscreen = fs;\n    }\n\n    if (requestsMX.has_value() && !(m_suppressedEvents & SUPPRESS_MAXIMIZE)) {\n        if (m_isMapped) {\n            auto window    = m_self.lock();\n            auto state     = sc<int8_t>(window->m_fullscreenState.client);\n            bool maximized = (state & sc<uint8_t>(FSMODE_MAXIMIZED)) != 0;\n            g_pCompositor->changeWindowFullscreenModeClient(window, FSMODE_MAXIMIZED, !maximized);\n        }\n    }\n}\n\nvoid CWindow::onUpdateMeta() {\n    const auto NEWTITLE = fetchTitle();\n    bool       doUpdate = false;\n\n    if (m_title != NEWTITLE) {\n        m_title = NEWTITLE;\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"windowtitle\", .data = std::format(\"{:x}\", rc<uintptr_t>(this))});\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"windowtitlev2\", .data = std::format(\"{:x},{}\", rc<uintptr_t>(this), m_title)});\n        Event::bus()->m_events.window.title.emit(m_self.lock());\n\n        if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others\n            g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindow\", .data = m_class + \",\" + m_title});\n            g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindowv2\", .data = std::format(\"{:x}\", rc<uintptr_t>(this))});\n\n            // no need for a hook event\n        }\n\n        Log::logger->log(Log::DEBUG, \"Window {:x} set title to {}\", rc<uintptr_t>(this), m_title);\n        doUpdate = true;\n    }\n\n    const auto NEWCLASS = fetchClass();\n    if (m_class != NEWCLASS) {\n        m_class = NEWCLASS;\n\n        Event::bus()->m_events.window.class_.emit(m_self.lock());\n\n        if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others\n            g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindow\", .data = m_class + \",\" + m_title});\n            g_pEventManager->postEvent(SHyprIPCEvent{.event = \"activewindowv2\", .data = std::format(\"{:x}\", rc<uintptr_t>(this))});\n\n            // no need for a hook event\n        }\n\n        Log::logger->log(Log::DEBUG, \"Window {:x} set class to {}\", rc<uintptr_t>(this), m_class);\n        doUpdate = true;\n    }\n\n    if (doUpdate) {\n        m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TITLE | Desktop::Rule::RULE_PROP_CLASS);\n        updateToplevel();\n    }\n}\n\nstd::string CWindow::fetchTitle() {\n    if (!m_isX11) {\n        if (m_xdgSurface && m_xdgSurface->m_toplevel)\n            return m_xdgSurface->m_toplevel->m_state.title;\n    } else {\n        if (m_xwaylandSurface)\n            return m_xwaylandSurface->m_state.title;\n    }\n\n    return \"\";\n}\n\nstd::string CWindow::fetchClass() {\n    if (!m_isX11) {\n        if (m_xdgSurface && m_xdgSurface->m_toplevel)\n            return m_xdgSurface->m_toplevel->m_state.appid;\n    } else {\n        if (m_xwaylandSurface)\n            return m_xwaylandSurface->m_state.appid;\n    }\n\n    return \"\";\n}\n\nvoid CWindow::onAck(uint32_t serial) {\n    const auto SERIAL = std::ranges::find_if(m_pendingSizeAcks | std::views::reverse, [serial](const auto& e) { return e.first <= serial; });\n\n    if (SERIAL == m_pendingSizeAcks.rend())\n        return;\n\n    m_pendingSizeAck = *SERIAL;\n    std::erase_if(m_pendingSizeAcks, [&](const auto& el) { return el.first <= SERIAL->first; });\n\n    if (m_isX11)\n        return;\n\n    m_wlSurface->resource()->m_pending.ackedSize          = m_pendingSizeAck->second; // apply pending size. We pinged, the window ponged.\n    m_wlSurface->resource()->m_pending.updated.bits.acked = true;\n    m_pendingSizeAck.reset();\n}\n\nvoid CWindow::onResourceChangeX11() {\n    if (m_xwaylandSurface->m_surface && !m_wlSurface->resource())\n        m_wlSurface->assign(m_xwaylandSurface->m_surface.lock(), m_self.lock());\n    else if (!m_xwaylandSurface->m_surface && m_wlSurface->resource())\n        m_wlSurface->unassign();\n\n    // update metadata as well,\n    // could be first assoc and we need to catch the class\n    onUpdateMeta();\n\n    Log::logger->log(Log::DEBUG, \"xwayland window {:x} -> association to {:x}\", rc<uintptr_t>(m_xwaylandSurface.get()), rc<uintptr_t>(m_wlSurface->resource().get()));\n}\n\nvoid CWindow::onX11ConfigureRequest(CBox box) {\n\n    if (!m_xwaylandSurface->m_surface || !m_xwaylandSurface->m_mapped || !m_isMapped) {\n        m_xwaylandSurface->configure(box);\n        m_pendingReportedSize = box.size();\n        m_reportedSize        = box.size();\n        m_reportedPosition    = box.pos();\n        updateX11SurfaceScale();\n        return;\n    }\n\n    g_pHyprRenderer->damageWindow(m_self.lock());\n\n    if (!m_isFloating || isFullscreen() || g_layoutManager->dragController()->target() == m_self) {\n        sendWindowSize(true);\n        g_pInputManager->refocus();\n        g_pHyprRenderer->damageWindow(m_self.lock());\n        return;\n    }\n\n    if (box.size() > Vector2D{1, 1})\n        setHidden(false);\n    else\n        setHidden(true);\n\n    m_realPosition->setValueAndWarp(xwaylandPositionToReal(box.pos()));\n    m_realSize->setValueAndWarp(xwaylandSizeToReal(box.size()));\n\n    m_position = m_realPosition->goal();\n    m_size     = m_realSize->goal();\n\n    if (m_pendingReportedSize != box.size() || m_reportedPosition != box.pos()) {\n        m_xwaylandSurface->configure(box);\n        m_reportedSize        = box.size();\n        m_pendingReportedSize = box.size();\n        m_reportedPosition    = box.pos();\n    }\n\n    updateX11SurfaceScale();\n    updateWindowDecos();\n\n    if (!m_workspace || !m_workspace->isVisible())\n        return; // further things are only for visible windows\n\n    const auto monitorByRequestedPosition = g_pCompositor->getMonitorFromVector(m_realPosition->goal() + m_realSize->goal() / 2.f);\n    const auto currentMonitor             = m_workspace->m_monitor.lock();\n\n    Log::logger->log(\n        Log::DEBUG,\n        \"onX11ConfigureRequest: window '{}' ({:#x}) - workspace '{}' (special={}), currentMonitor='{}', monitorByRequestedPosition='{}', pos={:.0f},{:.0f}, size={:.0f},{:.0f}\",\n        m_title, (uintptr_t)this, m_workspace->m_name, m_workspace->m_isSpecialWorkspace, currentMonitor ? currentMonitor->m_name : \"null\",\n        monitorByRequestedPosition ? monitorByRequestedPosition->m_name : \"null\", m_realPosition->goal().x, m_realPosition->goal().y, m_realSize->goal().x, m_realSize->goal().y);\n\n    // Reassign workspace only when moving to a different monitor and not on a special workspace\n    // X11 apps send configure requests with positions based on XWayland's monitor layout, such as \"0,0\",\n    // which would incorrectly move windows off special workspaces\n    if (monitorByRequestedPosition && monitorByRequestedPosition != currentMonitor && !m_workspace->m_isSpecialWorkspace) {\n        Log::logger->log(Log::DEBUG, \"onX11ConfigureRequest: reassigning workspace from '{}' to '{}'\", m_workspace->m_name, monitorByRequestedPosition->m_activeWorkspace->m_name);\n        m_workspace = monitorByRequestedPosition->m_activeWorkspace;\n    }\n\n    g_pCompositor->changeWindowZOrder(m_self.lock(), true);\n\n    m_createdOverFullscreen = true;\n\n    g_pHyprRenderer->damageWindow(m_self.lock());\n}\n\nvoid CWindow::warpCursor(bool force) {\n    static auto PERSISTENTWARPS        = CConfigValue<Hyprlang::INT>(\"cursor:persistent_warps\");\n    const auto  coords                 = m_relativeCursorCoordsOnLastWarp;\n    m_relativeCursorCoordsOnLastWarp.x = -1; // reset m_vRelativeCursorCoordsOnLastWarp\n\n    if (*PERSISTENTWARPS && coords.x > 0 && coords.y > 0 && coords < m_size) // don't warp cursor outside the window\n        g_pCompositor->warpCursorTo(m_position + coords, force);\n    else\n        g_pCompositor->warpCursorTo(middle(), force);\n}\n\nPHLWINDOW CWindow::getSwallower() {\n    static auto PSWALLOWREGEX   = CConfigValue<std::string>(\"misc:swallow_regex\");\n    static auto PSWALLOWEXREGEX = CConfigValue<std::string>(\"misc:swallow_exception_regex\");\n    static auto PSWALLOW        = CConfigValue<Hyprlang::INT>(\"misc:enable_swallow\");\n\n    if (!*PSWALLOW || std::string{*PSWALLOWREGEX} == STRVAL_EMPTY || (*PSWALLOWREGEX).empty())\n        return nullptr;\n\n    // check parent\n    std::vector<PHLWINDOW> candidates;\n    pid_t                  currentPid = getPID();\n    // walk up the tree until we find someone, 25 iterations max.\n    for (size_t i = 0; i < 25; ++i) {\n        currentPid = getPPIDof(currentPid);\n\n        if (!currentPid)\n            break;\n\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped || w->isHidden())\n                continue;\n\n            if (w->getPID() == currentPid)\n                candidates.push_back(w);\n        }\n    }\n\n    if (!(*PSWALLOWREGEX).empty())\n        std::erase_if(candidates, [&](const auto& other) { return !RE2::FullMatch(other->m_class, *PSWALLOWREGEX); });\n\n    if (candidates.empty())\n        return nullptr;\n\n    if (!(*PSWALLOWEXREGEX).empty())\n        std::erase_if(candidates, [&](const auto& other) { return RE2::FullMatch(other->m_title, *PSWALLOWEXREGEX); });\n\n    if (candidates.empty())\n        return nullptr;\n\n    if (candidates.size() == 1)\n        return candidates[0];\n\n    // walk up the focus history and find the last focused\n    for (auto const& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) {\n        if (!w)\n            continue;\n\n        if (std::ranges::find(candidates.begin(), candidates.end(), w.lock()) != candidates.end())\n            return w.lock();\n    }\n\n    // if none are found (??) then just return the first one\n    return candidates[0];\n}\n\nbool CWindow::isX11OverrideRedirect() {\n    return m_xwaylandSurface && m_xwaylandSurface->m_overrideRedirect;\n}\n\nbool CWindow::isModal() {\n    return (m_xwaylandSurface && m_xwaylandSurface->m_modal);\n}\n\nVector2D CWindow::realToReportSize() {\n    if (!m_isX11)\n        return m_realSize->goal().clamp(Vector2D{0, 0}, Math::VECTOR2D_MAX);\n\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    const auto  REPORTSIZE = m_realSize->goal().clamp(Vector2D{1, 1}, Math::VECTOR2D_MAX);\n    const auto  PMONITOR   = m_monitor.lock();\n\n    if (*PXWLFORCESCALEZERO && PMONITOR)\n        // Keep X11 configure sizes integral to avoid truncation (e.g. 2879.999 -> 2879) later in xcb.\n        return (REPORTSIZE * PMONITOR->m_scale).round();\n\n    return REPORTSIZE;\n}\n\nVector2D CWindow::realToReportPosition() {\n    if (!m_isX11)\n        return m_realPosition->goal();\n\n    return g_pXWaylandManager->waylandToXWaylandCoords(m_realPosition->goal());\n}\n\nVector2D CWindow::xwaylandSizeToReal(Vector2D size) {\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    const auto  PMONITOR = m_monitor.lock();\n    const auto  SIZE     = size.clamp(Vector2D{1, 1}, Math::VECTOR2D_MAX);\n    const auto  SCALE    = *PXWLFORCESCALEZERO ? PMONITOR->m_scale : 1.0f;\n\n    return SIZE / SCALE;\n}\n\nVector2D CWindow::xwaylandPositionToReal(Vector2D pos) {\n    return g_pXWaylandManager->xwaylandToWaylandCoords(pos);\n}\n\nvoid CWindow::updateX11SurfaceScale() {\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    m_X11SurfaceScaledBy = 1.0f;\n    if (m_isX11 && *PXWLFORCESCALEZERO) {\n        if (const auto PMONITOR = m_monitor.lock(); PMONITOR)\n            m_X11SurfaceScaledBy = PMONITOR->m_scale;\n    }\n}\n\nvoid CWindow::sendWindowSize(bool force) {\n    const auto PMONITOR = m_monitor.lock();\n\n    Log::logger->log(Log::TRACE, \"sendWindowSize: window:{:x},title:{} with real pos {}, real size {} (force: {})\", rc<uintptr_t>(this), this->m_title, m_realPosition->goal(),\n                     m_realSize->goal(), force);\n\n    // TODO: this should be decoupled from setWindowSize IMO\n    const auto REPORTPOS = realToReportPosition();\n\n    const auto REPORTSIZE = realToReportSize();\n\n    if (!force && m_pendingReportedSize == REPORTSIZE && (m_reportedPosition == REPORTPOS || !m_isX11))\n        return;\n\n    m_reportedPosition    = REPORTPOS;\n    m_pendingReportedSize = REPORTSIZE;\n    updateX11SurfaceScale();\n\n    if (m_isX11 && m_xwaylandSurface)\n        m_xwaylandSurface->configure({REPORTPOS, REPORTSIZE});\n    else if (m_xdgSurface && m_xdgSurface->m_toplevel)\n        m_pendingSizeAcks.emplace_back(m_xdgSurface->m_toplevel->setSize(REPORTSIZE), REPORTSIZE.floor());\n}\n\nNContentType::eContentType CWindow::getContentType() {\n    if (!m_wlSurface || !m_wlSurface->resource() || !m_wlSurface->resource()->m_contentType.valid())\n        return CONTENT_TYPE_NONE;\n\n    return m_wlSurface->resource()->m_contentType->m_value;\n}\n\nvoid CWindow::setContentType(NContentType::eContentType contentType) {\n    if (!m_wlSurface->resource()->m_contentType.valid())\n        m_wlSurface->resource()->m_contentType = PROTO::contentType->getContentType(m_wlSurface->resource());\n    // else disallow content type change if proto is used?\n\n    Log::logger->log(Log::INFO, \"ContentType for window {}\", sc<int>(contentType));\n    m_wlSurface->resource()->m_contentType->m_value = contentType;\n}\n\nvoid CWindow::deactivateGroupMembers() {\n    if (!m_group)\n        return;\n    for (const auto& w : m_group->windows()) {\n        if (w != m_self.lock()) {\n            // we don't want to deactivate unfocused xwayland windows\n            // because X is weird, keep the behavior for wayland windows\n            // also its not really needed for xwayland windows\n            // ref: #9760 #9294\n            if (!w->m_isX11 && w->m_xdgSurface && w->m_xdgSurface->m_toplevel)\n                w->m_xdgSurface->m_toplevel->setActive(false);\n        }\n    }\n}\n\nbool CWindow::isNotResponding() {\n    return g_pANRManager->isNotResponding(m_self.lock());\n}\n\nstd::optional<std::string> CWindow::xdgTag() {\n    if (!m_xdgSurface || !m_xdgSurface->m_toplevel)\n        return std::nullopt;\n\n    return m_xdgSurface->m_toplevel->m_toplevelTag;\n}\n\nstd::optional<std::string> CWindow::xdgDescription() {\n    if (!m_xdgSurface || !m_xdgSurface->m_toplevel)\n        return std::nullopt;\n\n    return m_xdgSurface->m_toplevel->m_toplevelDescription;\n}\n\nPHLWINDOW CWindow::parent() {\n    if (m_isX11) {\n        auto t = x11TransientFor();\n\n        // don't return a parent that's not mapped\n        if (!validMapped(t))\n            return nullptr;\n\n        return t;\n    }\n\n    if (!m_xdgSurface || !m_xdgSurface->m_toplevel || !m_xdgSurface->m_toplevel->m_parent)\n        return nullptr;\n\n    // don't return a parent that's not mapped\n    if (!m_xdgSurface->m_toplevel->m_parent->m_window || !validMapped(m_xdgSurface->m_toplevel->m_parent->m_window))\n        return nullptr;\n\n    return m_xdgSurface->m_toplevel->m_parent->m_window.lock();\n}\n\nbool CWindow::priorityFocus() {\n    return !m_isX11 && CAsyncDialogBox::isPriorityDialogBox(getPID());\n}\n\nSP<CWLSurfaceResource> CWindow::getSolitaryResource() {\n    if (!m_wlSurface || !m_wlSurface->resource())\n        return nullptr;\n\n    auto res = m_wlSurface->resource();\n    if (m_isX11)\n        return res;\n\n    if (popupsCount())\n        return nullptr;\n\n    if (res->m_subsurfaces.size() == 0)\n        return res;\n\n    if (res->m_subsurfaces.size() >= 1) {\n        if (!res->hasVisibleSubsurface())\n            return res;\n\n        if (res->m_subsurfaces.size() == 1) {\n            if (res->m_subsurfaces[0].expired() || res->m_subsurfaces[0]->m_surface.expired())\n                return nullptr;\n            auto surf = res->m_subsurfaces[0]->m_surface.lock();\n            if (!surf || surf->m_subsurfaces.size() != 0 || surf->extends() != res->extends() || !surf->m_current.texture || !surf->m_current.texture->m_opaque)\n                return nullptr;\n            return surf;\n        }\n    }\n\n    return nullptr;\n}\n\nVector2D CWindow::getReportedSize() {\n    if (m_isX11)\n        return m_reportedSize;\n    if (m_wlSurface && m_wlSurface->resource())\n        return m_wlSurface->resource()->m_current.ackedSize;\n    return m_reportedSize;\n}\n\nvoid CWindow::updateDecorationValues() {\n    static auto PACTIVECOL              = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.active_border\");\n    static auto PINACTIVECOL            = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.inactive_border\");\n    static auto PNOGROUPACTIVECOL       = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.nogroup_border_active\");\n    static auto PNOGROUPINACTIVECOL     = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:col.nogroup_border\");\n    static auto PGROUPACTIVECOL         = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_active\");\n    static auto PGROUPINACTIVECOL       = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_inactive\");\n    static auto PGROUPACTIVELOCKEDCOL   = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_locked_active\");\n    static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:col.border_locked_inactive\");\n    static auto PINACTIVEALPHA          = CConfigValue<Hyprlang::FLOAT>(\"decoration:inactive_opacity\");\n    static auto PACTIVEALPHA            = CConfigValue<Hyprlang::FLOAT>(\"decoration:active_opacity\");\n    static auto PFULLSCREENALPHA        = CConfigValue<Hyprlang::FLOAT>(\"decoration:fullscreen_opacity\");\n    static auto PSHADOWCOL              = CConfigValue<Hyprlang::INT>(\"decoration:shadow:color\");\n    static auto PSHADOWCOLINACTIVE      = CConfigValue<Hyprlang::INT>(\"decoration:shadow:color_inactive\");\n    static auto PDIMSTRENGTH            = CConfigValue<Hyprlang::FLOAT>(\"decoration:dim_strength\");\n    static auto PDIMENABLED             = CConfigValue<Hyprlang::INT>(\"decoration:dim_inactive\");\n    static auto PDIMMODAL               = CConfigValue<Hyprlang::INT>(\"decoration:dim_modal\");\n\n    auto* const ACTIVECOL              = sc<CGradientValueData*>((PACTIVECOL.ptr())->getData());\n    auto* const INACTIVECOL            = sc<CGradientValueData*>((PINACTIVECOL.ptr())->getData());\n    auto* const NOGROUPACTIVECOL       = sc<CGradientValueData*>((PNOGROUPACTIVECOL.ptr())->getData());\n    auto* const NOGROUPINACTIVECOL     = sc<CGradientValueData*>((PNOGROUPINACTIVECOL.ptr())->getData());\n    auto* const GROUPACTIVECOL         = sc<CGradientValueData*>((PGROUPACTIVECOL.ptr())->getData());\n    auto* const GROUPINACTIVECOL       = sc<CGradientValueData*>((PGROUPINACTIVECOL.ptr())->getData());\n    auto* const GROUPACTIVELOCKEDCOL   = sc<CGradientValueData*>((PGROUPACTIVELOCKEDCOL.ptr())->getData());\n    auto* const GROUPINACTIVELOCKEDCOL = sc<CGradientValueData*>((PGROUPINACTIVELOCKEDCOL.ptr())->getData());\n\n    auto        setBorderColor = [&](CGradientValueData grad) -> void {\n        if (grad == m_realBorderColor)\n            return;\n\n        m_realBorderColorPrevious = m_realBorderColor;\n        m_realBorderColor         = grad;\n        m_borderFadeAnimationProgress->setValueAndWarp(0.f);\n        *m_borderFadeAnimationProgress = 1.f;\n    };\n\n    const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal();\n\n    const bool GROUPLOCKED = m_group ? m_group->locked() : false;\n    if (m_self == Desktop::focusState()->window()) {\n        const auto* const ACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);\n        setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR));\n    } else {\n        const auto* const INACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);\n        setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR));\n    }\n\n    // opacity\n    const auto PWORKSPACE = m_workspace;\n    if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) {\n        *m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA);\n    } else {\n        if (m_self == Desktop::focusState()->window())\n            *m_activeInactiveAlpha = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA);\n        else\n            *m_activeInactiveAlpha = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA);\n    }\n\n    // dim\n    float goalDim = 1.F;\n    if (m_self == Desktop::focusState()->window() || m_ruleApplicator->noDim().valueOrDefault() || !*PDIMENABLED)\n        goalDim = 0;\n    else\n        goalDim = *PDIMSTRENGTH;\n\n    if (IS_SHADOWED_BY_MODAL && *PDIMMODAL)\n        goalDim += (1.F - goalDim) / 2.F;\n\n    *m_dimPercent = goalDim;\n\n    // shadow\n    if (!isX11OverrideRedirect() && !m_X11DoesntWantBorders) {\n        if (m_self == Desktop::focusState()->window())\n            *m_realShadowColor = CHyprColor(*PSHADOWCOL);\n        else\n            *m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL);\n    } else\n        m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow\n\n    updateWindowDecos();\n}\n\nstd::optional<double> CWindow::calculateSingleExpr(const std::string& s) {\n    const auto        PMONITOR     = m_monitor ? m_monitor : Desktop::focusState()->monitor();\n    const auto        CURSOR_LOCAL = g_pInputManager->getMouseCoordsInternal() - (PMONITOR ? PMONITOR->m_position : Vector2D{});\n\n    Math::CExpression expr;\n    expr.addVariable(\"window_w\", m_realSize->goal().x);\n    expr.addVariable(\"window_h\", m_realSize->goal().y);\n    expr.addVariable(\"window_x\", m_realPosition->goal().x - (PMONITOR ? PMONITOR->m_position.x : 0));\n    expr.addVariable(\"window_y\", m_realPosition->goal().y - (PMONITOR ? PMONITOR->m_position.y : 0));\n\n    expr.addVariable(\"monitor_w\", PMONITOR ? PMONITOR->m_size.x : 1920);\n    expr.addVariable(\"monitor_h\", PMONITOR ? PMONITOR->m_size.y : 1080);\n\n    expr.addVariable(\"cursor_x\", CURSOR_LOCAL.x);\n    expr.addVariable(\"cursor_y\", CURSOR_LOCAL.y);\n\n    return expr.compute(s);\n}\n\nstd::optional<Vector2D> CWindow::calculateExpression(const std::string& s) {\n    auto spacePos = s.find(' ');\n    if (spacePos == std::string::npos)\n        return std::nullopt;\n\n    const auto LHS = calculateSingleExpr(s.substr(0, spacePos));\n    const auto RHS = calculateSingleExpr(s.substr(spacePos + 1));\n\n    if (!LHS || !RHS)\n        return std::nullopt;\n\n    return Vector2D{*LHS, *RHS};\n}\n\nstatic void setVector2DAnimToMove(WP<CBaseAnimatedVariable> pav) {\n    if (!pav)\n        return;\n\n    CAnimatedVariable<Vector2D>* animvar = dc<CAnimatedVariable<Vector2D>*>(pav.get());\n    animvar->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"windowsMove\"));\n\n    if (animvar->m_Context.pWindow)\n        animvar->m_Context.pWindow->m_animatingIn = false;\n}\n\nvoid CWindow::mapWindow() {\n    static auto PINACTIVEALPHA     = CConfigValue<Hyprlang::FLOAT>(\"decoration:inactive_opacity\");\n    static auto PACTIVEALPHA       = CConfigValue<Hyprlang::FLOAT>(\"decoration:active_opacity\");\n    static auto PDIMSTRENGTH       = CConfigValue<Hyprlang::FLOAT>(\"decoration:dim_strength\");\n    static auto PNEWTAKESOVERFS    = CConfigValue<Hyprlang::INT>(\"misc:on_focus_under_fullscreen\");\n    static auto PINITIALWSTRACKING = CConfigValue<Hyprlang::INT>(\"misc:initial_workspace_tracking\");\n    static auto PAUTOGROUP         = CConfigValue<Hyprlang::INT>(\"group:auto_group\");\n\n    const auto  LAST_FOCUS_WINDOW = Desktop::focusState()->window();\n    const bool  IS_LAST_IN_FS     = LAST_FOCUS_WINDOW ? LAST_FOCUS_WINDOW->m_fullscreenState.internal != FSMODE_NONE : false;\n    const auto  LAST_FS_MODE      = LAST_FOCUS_WINDOW ? LAST_FOCUS_WINDOW->m_fullscreenState.internal : FSMODE_NONE;\n\n    auto        PMONITOR = Desktop::focusState()->monitor();\n    if (!Desktop::focusState()->monitor()) {\n        Desktop::focusState()->rawMonitorFocus(g_pCompositor->getMonitorFromVector({}));\n        PMONITOR = Desktop::focusState()->monitor();\n    }\n    auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace;\n    m_monitor       = PMONITOR;\n    m_workspace     = PWORKSPACE;\n    m_isMapped      = true;\n    m_readyToDelete = false;\n    m_fadingOut     = false;\n    m_title         = fetchTitle();\n    m_firstMap      = true;\n    m_initialTitle  = m_title;\n    m_initialClass  = fetchClass();\n\n    // check for token\n    std::string requestedWorkspace = \"\";\n    bool        workspaceSilent    = false;\n\n    if (*PINITIALWSTRACKING) {\n        const auto WINDOWENV = getEnv();\n        if (WINDOWENV.contains(\"HL_INITIAL_WORKSPACE_TOKEN\")) {\n            const auto SZTOKEN = WINDOWENV.at(\"HL_INITIAL_WORKSPACE_TOKEN\");\n            Log::logger->log(Log::DEBUG, \"New window contains HL_INITIAL_WORKSPACE_TOKEN: {}\", SZTOKEN);\n            const auto TOKEN = g_pTokenManager->getToken(SZTOKEN);\n            if (TOKEN) {\n                // find workspace and use it\n                Desktop::View::SInitialWorkspaceToken WS = std::any_cast<Desktop::View::SInitialWorkspaceToken>(TOKEN->m_data);\n\n                Log::logger->log(Log::DEBUG, \"HL_INITIAL_WORKSPACE_TOKEN {} -> {}\", SZTOKEN, WS.workspace);\n\n                if (g_pCompositor->getWorkspaceByString(WS.workspace) != m_workspace) {\n                    requestedWorkspace = WS.workspace;\n                    workspaceSilent    = true;\n                }\n\n                if (*PINITIALWSTRACKING == 1) // one-shot token\n                    g_pTokenManager->removeToken(TOKEN);\n                else if (*PINITIALWSTRACKING == 2) { // persistent\n                    if (WS.primaryOwner.expired()) {\n                        WS.primaryOwner = m_self.lock();\n                        TOKEN->m_data   = WS;\n                    }\n\n                    m_initialWorkspaceToken = SZTOKEN;\n                }\n            }\n        }\n    }\n\n    if (g_pInputManager->m_lastFocusOnLS) // waybar fix\n        g_pInputManager->releaseAllMouseButtons();\n\n    // checks if the window wants borders and sets the appropriate flag\n    g_pXWaylandManager->checkBorders(m_self.lock());\n\n    // registers the animated vars and stuff\n    onMap();\n\n    if (g_pXWaylandManager->shouldBeFloated(m_self.lock())) {\n        m_isFloating    = true;\n        m_requestsFloat = true;\n    }\n\n    m_X11ShouldntFocus = m_X11ShouldntFocus || (m_isX11 && isX11OverrideRedirect() && !m_xwaylandSurface->wantsFocus());\n\n    // window rules\n    std::optional<eFullscreenMode>                 requestedInternalFSMode, requestedClientFSMode;\n    std::optional<Desktop::View::SFullscreenState> requestedFSState;\n    if (m_wantsInitialFullscreen || (m_isX11 && m_xwaylandSurface->m_fullscreen))\n        requestedClientFSMode = FSMODE_FULLSCREEN;\n    MONITORID requestedFSMonitor = m_wantsInitialFullscreenMonitor;\n\n    auto      setStaticProps = [&]() {\n        if (!m_ruleApplicator->static_.monitor.empty()) {\n            const auto& MONITORSTR = m_ruleApplicator->static_.monitor;\n            if (MONITORSTR == \"unset\")\n                m_monitor = PMONITOR;\n            else {\n                const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR);\n\n                if (MONITOR) {\n                    m_monitor = MONITOR;\n\n                    const auto PMONITORFROMID = m_monitor.lock();\n\n                    if (m_monitor != PMONITOR) {\n                        g_pKeybindManager->m_dispatchers[\"focusmonitor\"](std::to_string(monitorID()));\n                        PMONITOR = PMONITORFROMID;\n                    }\n                    m_workspace = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace;\n                    PWORKSPACE  = m_workspace;\n\n                    Log::logger->log(Log::DEBUG, \"Rule monitor, applying to {:mw}\", m_self.lock());\n                    requestedFSMonitor = MONITOR_INVALID;\n                } else\n                    Log::logger->log(Log::ERR, \"No monitor in monitor {} rule\", MONITORSTR);\n            }\n        }\n\n        if (!m_ruleApplicator->static_.workspace.empty()) {\n            const auto WORKSPACERQ = m_ruleApplicator->static_.workspace;\n\n            if (WORKSPACERQ == \"unset\")\n                requestedWorkspace = \"\";\n            else\n                requestedWorkspace = WORKSPACERQ;\n\n            const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ;\n\n            if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == \"name:\" + PWORKSPACE->m_name)\n                requestedWorkspace = \"\";\n\n            Log::logger->log(Log::DEBUG, \"Rule workspace matched by {}, {} applied.\", m_self.lock(), m_ruleApplicator->static_.workspace);\n            requestedFSMonitor = MONITOR_INVALID;\n        }\n\n        m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating);\n        m_target->setPseudo(m_ruleApplicator->static_.pseudo.value_or(m_target->isPseudo()));\n        m_noInitialFocus = m_ruleApplicator->static_.noInitialFocus.value_or(m_noInitialFocus);\n        m_pinned         = m_ruleApplicator->static_.pin.value_or(m_pinned);\n\n        if (m_ruleApplicator->static_.fullscreenStateClient || m_ruleApplicator->static_.fullscreenStateInternal) {\n            requestedFSState = Desktop::View::SFullscreenState{\n                .internal = sc<eFullscreenMode>(m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)),\n                .client   = sc<eFullscreenMode>(m_ruleApplicator->static_.fullscreenStateClient.value_or(0)),\n            };\n        }\n\n        if (!m_ruleApplicator->static_.suppressEvent.empty()) {\n            for (const auto& var : m_ruleApplicator->static_.suppressEvent) {\n                if (var == \"fullscreen\")\n                    m_suppressedEvents |= Desktop::View::SUPPRESS_FULLSCREEN;\n                else if (var == \"maximize\")\n                    m_suppressedEvents |= Desktop::View::SUPPRESS_MAXIMIZE;\n                else if (var == \"activate\")\n                    m_suppressedEvents |= Desktop::View::SUPPRESS_ACTIVATE;\n                else if (var == \"activatefocus\")\n                    m_suppressedEvents |= Desktop::View::SUPPRESS_ACTIVATE_FOCUSONLY;\n                else if (var == \"fullscreenoutput\")\n                    m_suppressedEvents |= Desktop::View::SUPPRESS_FULLSCREEN_OUTPUT;\n                else\n                    Log::logger->log(Log::ERR, \"Error while parsing suppressevent windowrule: unknown event type {}\", var);\n            }\n        }\n\n        if (m_ruleApplicator->static_.fullscreen.value_or(false))\n            requestedInternalFSMode = FSMODE_FULLSCREEN;\n\n        if (m_ruleApplicator->static_.maximize.value_or(false))\n            requestedInternalFSMode = FSMODE_MAXIMIZED;\n\n        if (!m_ruleApplicator->static_.group.empty()) {\n            if (!(m_groupRules & Desktop::View::GROUP_OVERRIDE) && trim(m_ruleApplicator->static_.group) != \"group\") {\n                CVarList2   vars(std::string{m_ruleApplicator->static_.group}, 0, 's');\n                std::string vPrev = \"\";\n\n                for (auto const& v : vars) {\n                    if (v == \"group\")\n                        continue;\n\n                    if (v == \"set\") {\n                        m_groupRules |= Desktop::View::GROUP_SET;\n                    } else if (v == \"new\") {\n                        // shorthand for `group barred set`\n                        m_groupRules |= (Desktop::View::GROUP_SET | Desktop::View::GROUP_BARRED);\n                    } else if (v == \"lock\") {\n                        m_groupRules |= Desktop::View::GROUP_LOCK;\n                    } else if (v == \"invade\") {\n                        m_groupRules |= Desktop::View::GROUP_INVADE;\n                    } else if (v == \"barred\") {\n                        m_groupRules |= Desktop::View::GROUP_BARRED;\n                    } else if (v == \"deny\") {\n                        m_groupRules |= Desktop::View::GROUP_DENY;\n                    } else if (v == \"override\") {\n                        // Clear existing rules\n                        m_groupRules = Desktop::View::GROUP_OVERRIDE;\n                    } else if (v == \"unset\") {\n                        // Clear existing rules and stop processing\n                        m_groupRules = Desktop::View::GROUP_OVERRIDE;\n                        break;\n                    } else if (v == \"always\") {\n                        if (vPrev == \"set\" || vPrev == \"group\")\n                            m_groupRules |= Desktop::View::GROUP_SET_ALWAYS;\n                        else if (vPrev == \"lock\")\n                            m_groupRules |= Desktop::View::GROUP_LOCK_ALWAYS;\n                        else\n                            Log::logger->log(Log::ERR, \"windowrule `group` does not support `{} always`\", vPrev);\n                    }\n                    vPrev = v;\n                }\n            }\n        }\n\n        if (m_ruleApplicator->static_.content)\n            setContentType(sc<NContentType::eContentType>(m_ruleApplicator->static_.content.value()));\n\n        if (m_ruleApplicator->static_.noCloseFor)\n            m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(m_ruleApplicator->static_.noCloseFor.value());\n    };\n\n    const bool recheck = m_ruleApplicator->readStaticRules();\n    setStaticProps();\n    if (recheck) {\n        m_ruleApplicator->recheckStaticRules();\n        setStaticProps();\n    }\n\n    // make it uncloseable if it's a Hyprland dialog\n    // TODO: make some closeable?\n    if (CAsyncDialogBox::isAsyncDialogBox(getPID()))\n        m_closeableSince = Time::steadyNow() + std::chrono::years(10 /* Should be enough, no? */);\n\n    // disallow tiled pinned\n    if (m_pinned && !m_isFloating)\n        m_pinned = false;\n\n    CVarList2 WORKSPACEARGS = CVarList2(std::move(requestedWorkspace), 0, ' ', false, false);\n\n    if (!WORKSPACEARGS[0].empty()) {\n        WORKSPACEID requestedWorkspaceID;\n        std::string requestedWorkspaceName;\n        if (WORKSPACEARGS.contains(\"silent\"))\n            workspaceSilent = true;\n\n        auto joined = WORKSPACEARGS.join(\" \", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0);\n        if (joined.starts_with(\"empty\") && PWORKSPACE->getWindows() == 0) {\n            requestedWorkspaceID   = PWORKSPACE->m_id;\n            requestedWorkspaceName = PWORKSPACE->m_name;\n        } else {\n            auto result            = getWorkspaceIDNameFromString(joined);\n            requestedWorkspaceID   = result.id;\n            requestedWorkspaceName = result.name;\n        }\n\n        if (requestedWorkspaceID != WORKSPACE_INVALID) {\n            auto pWorkspace = g_pCompositor->getWorkspaceByID(requestedWorkspaceID);\n\n            if (!pWorkspace)\n                pWorkspace = g_pCompositor->createNewWorkspace(requestedWorkspaceID, monitorID(), requestedWorkspaceName, false);\n\n            PWORKSPACE = pWorkspace;\n\n            m_workspace = pWorkspace;\n            m_monitor   = pWorkspace->m_monitor;\n\n            if (m_monitor.lock()->m_activeSpecialWorkspace && !pWorkspace->m_isSpecialWorkspace)\n                workspaceSilent = true;\n\n            if (!workspaceSilent) {\n                if (pWorkspace->m_isSpecialWorkspace)\n                    pWorkspace->m_monitor->setSpecialWorkspace(pWorkspace);\n                else if (PMONITOR->activeWorkspaceID() != requestedWorkspaceID && !m_noInitialFocus)\n                    g_pKeybindManager->m_dispatchers[\"workspace\"](requestedWorkspaceName);\n\n                PMONITOR = Desktop::focusState()->monitor();\n            }\n\n            requestedFSMonitor = MONITOR_INVALID;\n        } else\n            workspaceSilent = false;\n    }\n\n    if (m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN_OUTPUT)\n        requestedFSMonitor = MONITOR_INVALID;\n    else if (requestedFSMonitor != MONITOR_INVALID) {\n        if (const auto PM = g_pCompositor->getMonitorFromID(requestedFSMonitor); PM)\n            m_monitor = PM;\n\n        const auto PMONITORFROMID = m_monitor.lock();\n\n        if (m_monitor != PMONITOR) {\n            g_pKeybindManager->m_dispatchers[\"focusmonitor\"](std::to_string(monitorID()));\n            PMONITOR = PMONITORFROMID;\n        }\n        m_workspace = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace;\n        PWORKSPACE  = m_workspace;\n\n        Log::logger->log(Log::DEBUG, \"Requested monitor, applying to {:mw}\", m_self.lock());\n    }\n\n    if (PWORKSPACE->m_defaultFloating)\n        m_isFloating = true;\n\n    if (PWORKSPACE->m_defaultPseudo) {\n        CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(m_self.lock());\n        m_target->setPseudoSize(Vector2D{desiredGeometry.width, desiredGeometry.height});\n        m_target->setPseudo(true);\n    }\n\n    updateWindowData();\n\n    // Verify window swallowing. Get the swallower before calling onWindowCreated(m_self.lock()) because getSwallower() wouldn't get it after if m_self.lock() gets auto grouped.\n    const auto SWALLOWER = getSwallower();\n    m_swallowed          = SWALLOWER;\n    if (m_swallowed)\n        m_swallowed->m_currentlySwallowed = true;\n\n    // emit the IPC event before the layout might focus the window to avoid a focus event first\n    g_pEventManager->postEvent(SHyprIPCEvent{\"openwindow\", std::format(\"{:x},{},{},{}\", m_self.lock(), PWORKSPACE->m_name, m_class, m_title)});\n    Event::bus()->m_events.window.openEarly.emit(m_self.lock());\n\n    if (*PAUTOGROUP                                                                        // auto_group enabled\n        && Desktop::focusState()->window()                                                 // focused window exists\n        && canBeGroupedInto(Desktop::focusState()->window()->m_group)                      // we can group\n        && Desktop::focusState()->window()->m_workspace == m_workspace                     // workspaces match, we're not opening on another ws\n        && !g_pXWaylandManager->shouldBeFloated(m_self.lock()) && !isX11OverrideRedirect() // not a window that should float or X11\n        && !(m_isFloating && !Desktop::focusState()->window()->m_isFloating)               // do not auto-group a floated window into a tiled group\n        && !isModal()                                                                      // no modal grouping\n    ) {\n        // add to group if we are focused on one\n        Desktop::focusState()->window()->m_group->add(m_self.lock());\n    } else\n        g_layoutManager->newTarget(m_target, m_workspace->m_space);\n\n    if (!m_group && (m_groupRules & GROUP_SET))\n        m_group = CGroup::create({m_self});\n\n    if (m_isFloating) {\n        m_createdOverFullscreen = true;\n\n        // set the pseudo size to the GOAL of our current size\n        // because the windows are animated on RealSize\n        m_target->setPseudoSize(m_realSize->goal());\n\n        g_pCompositor->changeWindowZOrder(m_self.lock(), true);\n    } else {\n        bool setPseudo = false;\n\n        if (!m_ruleApplicator->static_.size.empty()) {\n            const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.size);\n            if (!COMPUTED)\n                Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", m_ruleApplicator->static_.size);\n            else {\n                setPseudo = true;\n                m_target->setPseudoSize(*COMPUTED);\n                setHidden(false);\n            }\n        }\n\n        if (!setPseudo)\n            m_target->setPseudoSize(m_realSize->goal() - Vector2D(10, 10));\n    }\n\n    const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window();\n\n    if (m_ruleApplicator->allowsInput().valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception\n        m_ruleApplicator->noFocusOverride(Desktop::Types::COverridableVar(false, m_ruleApplicator->allowsInput().getPriority()));\n        m_noInitialFocus   = false;\n        m_X11ShouldntFocus = false;\n    }\n\n    // check LS focus grab\n    const auto PFORCEFOCUS  = g_pCompositor->getForceFocus();\n    const auto PLSFROMFOCUS = g_pCompositor->getLayerSurfaceFromSurface(Desktop::focusState()->surface());\n    if (PLSFROMFOCUS && PLSFROMFOCUS->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE)\n        m_noInitialFocus = true;\n\n    if (m_workspace->m_hasFullscreenWindow && !requestedInternalFSMode.has_value() && !requestedClientFSMode.has_value() && !m_isFloating) {\n        if (*PNEWTAKESOVERFS == 0)\n            m_noInitialFocus = true;\n        else if (*PNEWTAKESOVERFS == 1)\n            requestedInternalFSMode = m_workspace->m_fullscreenMode;\n        else if (*PNEWTAKESOVERFS == 2)\n            g_pCompositor->setWindowFullscreenInternal(m_workspace->getFullscreenWindow(), FSMODE_NONE);\n    }\n\n    if (!m_ruleApplicator->noFocus().valueOrDefault() && !m_noInitialFocus && (!isX11OverrideRedirect() || (m_isX11 && m_xwaylandSurface->wantsFocus())) && !workspaceSilent &&\n        (!PFORCEFOCUS || PFORCEFOCUS == m_self.lock()) && !g_pInputManager->isConstrained()) {\n\n        // this window should gain focus: if it's grouped, preserve fullscreen state.\n        const bool SAME_GROUP = m_group && m_group->has(LAST_FOCUS_WINDOW);\n\n        if (IS_LAST_IN_FS && SAME_GROUP) {\n            Desktop::focusState()->rawWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);\n            g_pCompositor->setWindowFullscreenInternal(m_self.lock(), LAST_FS_MODE);\n        } else\n            Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);\n\n        m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA);\n        m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH);\n    } else {\n        m_activeInactiveAlpha->setValueAndWarp(*PINACTIVEALPHA);\n        m_dimPercent->setValueAndWarp(0);\n    }\n\n    if (requestedClientFSMode.has_value() && (m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN))\n        requestedClientFSMode = sc<eFullscreenMode>(sc<uint8_t>(requestedClientFSMode.value_or(FSMODE_NONE)) & ~sc<uint8_t>(FSMODE_FULLSCREEN));\n    if (requestedClientFSMode.has_value() && (m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE))\n        requestedClientFSMode = sc<eFullscreenMode>(sc<uint8_t>(requestedClientFSMode.value_or(FSMODE_NONE)) & ~sc<uint8_t>(FSMODE_MAXIMIZED));\n\n    if (!m_noInitialFocus && (requestedInternalFSMode.has_value() || requestedClientFSMode.has_value() || requestedFSState.has_value())) {\n        // fix fullscreen on requested (basically do a switcheroo)\n        if (m_workspace->m_hasFullscreenWindow)\n            g_pCompositor->setWindowFullscreenInternal(m_workspace->getFullscreenWindow(), FSMODE_NONE);\n\n        m_realPosition->warp();\n        m_realSize->warp();\n        if (requestedFSState.has_value()) {\n            m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_WINDOW_RULE));\n            g_pCompositor->setWindowFullscreenState(m_self.lock(), requestedFSState.value());\n        } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !m_ruleApplicator->syncFullscreen().valueOrDefault())\n            g_pCompositor->setWindowFullscreenState(m_self.lock(),\n                                                    Desktop::View::SFullscreenState{.internal = requestedInternalFSMode.value(), .client = requestedClientFSMode.value()});\n        else if (requestedInternalFSMode.has_value())\n            g_pCompositor->setWindowFullscreenInternal(m_self.lock(), requestedInternalFSMode.value());\n        else if (requestedClientFSMode.has_value())\n            g_pCompositor->setWindowFullscreenClient(m_self.lock(), requestedClientFSMode.value());\n    }\n\n    // recheck idle inhibitors\n    g_pInputManager->recheckIdleInhibitorStatus();\n\n    updateToplevel();\n    m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL);\n\n    if (workspaceSilent) {\n        if (validMapped(PFOCUSEDWINDOWPREV)) {\n            Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV, FOCUS_REASON_NEW_WINDOW);\n            PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why\n        } else if (!PFOCUSEDWINDOWPREV)\n            Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON_NEW_WINDOW);\n    }\n\n    // swallow\n    if (SWALLOWER) {\n        g_layoutManager->removeTarget(SWALLOWER->layoutTarget());\n        SWALLOWER->setHidden(true);\n    }\n\n    m_firstMap = false;\n\n    Log::logger->log(Log::DEBUG, \"Map request dispatched, monitor {}, window pos: {:5j}, window size: {:5j}\", PMONITOR->m_name, m_realPosition->goal(), m_realSize->goal());\n\n    // emit the hook event here after basic stuff has been initialized\n    Event::bus()->m_events.window.open.emit(m_self.lock());\n\n    // apply data from default decos. Borders, shadows.\n    g_pDecorationPositioner->forceRecalcFor(m_self.lock());\n    updateWindowDecos();\n    layoutTarget()->recalc();\n\n    // do animations\n    g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN);\n\n    m_realPosition->setCallbackOnEnd(setVector2DAnimToMove);\n    m_realSize->setCallbackOnEnd(setVector2DAnimToMove);\n\n    // recalc the values for this window\n    updateDecorationValues();\n    // avoid this window being visible\n    if (PWORKSPACE->m_hasFullscreenWindow && !isFullscreen() && !m_isFloating)\n        m_alpha->setValueAndWarp(0.f);\n\n    g_pCompositor->setPreferredScaleForSurface(wlSurface()->resource(), PMONITOR->m_scale);\n    g_pCompositor->setPreferredTransformForSurface(wlSurface()->resource(), PMONITOR->m_transform);\n\n    if (g_pSeatManager->m_mouse.expired() || !g_pInputManager->isConstrained())\n        g_pInputManager->sendMotionEventsToFocused();\n\n    // fix some xwayland apps that don't behave nicely\n    m_reportedSize = m_pendingReportedSize;\n\n    if (m_workspace)\n        m_workspace->updateWindows();\n\n    if (PMONITOR && isX11OverrideRedirect()) {\n        static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n        if (*PXWLFORCESCALEZERO)\n            m_X11SurfaceScaledBy = PMONITOR->m_scale;\n    }\n}\n\nvoid CWindow::unmapWindow() {\n    Log::logger->log(Log::DEBUG, \"{:c} unmapped\", m_self.lock());\n\n    static auto PEXITRETAINSFS = CConfigValue<Hyprlang::INT>(\"misc:exit_window_retains_fullscreen\");\n\n    const auto  CURRENTWINDOWFSSTATE = isFullscreen();\n    const auto  CURRENTFSMODE        = m_fullscreenState.internal;\n\n    if (!wlSurface()->exists() || !m_isMapped) {\n        Log::logger->log(Log::WARN, \"{} unmapped without being mapped??\", m_self.lock());\n        m_fadingOut = false;\n        return;\n    }\n\n    const auto PMONITOR = m_monitor.lock();\n    if (PMONITOR) {\n        m_originalClosedPos     = m_realPosition->value() - PMONITOR->m_position;\n        m_originalClosedSize    = m_realSize->value();\n        m_originalClosedExtents = getFullWindowExtents();\n    }\n\n    m_events.unmap.emit();\n    g_pEventManager->postEvent(SHyprIPCEvent{\"closewindow\", std::format(\"{:x}\", m_self.lock())});\n    Event::bus()->m_events.window.close.emit(m_self.lock());\n\n    if (m_isFloating && !m_isX11 && m_ruleApplicator->persistentSize().valueOrDefault()) {\n        Log::logger->log(Log::DEBUG, \"storing floating size {}x{} for window {}::{} on close\", m_realSize->value().x, m_realSize->value().y, m_class, m_title);\n        g_pConfigManager->storeFloatingSize(m_self.lock(), m_realSize->value());\n    }\n\n    if (isFullscreen())\n        g_pCompositor->setWindowFullscreenInternal(m_self.lock(), FSMODE_NONE);\n\n    // Allow the renderer to catch the last frame.\n    if (g_pHyprRenderer->shouldRenderWindow(m_self.lock()))\n        g_pHyprRenderer->makeSnapshot(m_self.lock());\n\n    // swallowing\n    if (valid(m_swallowed)) {\n        if (m_swallowed->m_currentlySwallowed) {\n            m_swallowed->m_currentlySwallowed = false;\n            m_swallowed->setHidden(false);\n\n            if (m_group)\n                m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false.\n\n            g_layoutManager->newTarget(m_swallowed->layoutTarget(), m_workspace->m_space);\n        }\n\n        m_swallowed->m_groupSwallowed = false;\n        m_swallowed.reset();\n    }\n\n    bool      wasLastWindow = false;\n    PHLWINDOW nextInGroup   = [this] -> PHLWINDOW {\n        if (!m_group)\n            return nullptr;\n\n        // walk the history to find a suitable window\n        const auto HISTORY = Desktop::History::windowTracker()->fullHistory();\n        for (const auto& w : HISTORY | std::views::reverse) {\n            if (!w || !w->m_isMapped || w == m_self)\n                continue;\n\n            if (!m_group->has(w.lock()))\n                continue;\n\n            return w.lock();\n        }\n\n        return nullptr;\n    }();\n\n    if (m_self.lock() == Desktop::focusState()->window()) {\n        wasLastWindow = true;\n        Desktop::focusState()->resetWindowFocus();\n\n        g_pInputManager->releaseAllMouseButtons();\n    }\n\n    if (m_self.lock() == g_layoutManager->dragController()->target())\n        CKeybindManager::changeMouseBindMode(MBIND_INVALID);\n\n    // remove the fullscreen window status from workspace if we closed it\n    const auto PWORKSPACE = m_workspace;\n\n    if (PWORKSPACE->m_hasFullscreenWindow && isFullscreen())\n        PWORKSPACE->m_hasFullscreenWindow = false;\n\n    if (m_group)\n        m_group->remove(m_self.lock());\n\n    g_layoutManager->removeTarget(m_target);\n\n    g_pHyprRenderer->damageWindow(m_self.lock());\n\n    // do this after onWindowRemoved because otherwise it'll think the window is invalid\n    m_isMapped = false;\n\n    // refocus on a new window if needed\n    if (wasLastWindow) {\n        static auto FOCUSONCLOSE = CConfigValue<Hyprlang::INT>(\"input:focus_on_close\");\n        PHLWINDOW   candidate    = nextInGroup;\n\n        if (!candidate) {\n            if (*FOCUSONCLOSE)\n                candidate = (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),\n                                                                  Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING));\n            else {\n                const auto CAND = g_layoutManager->getNextCandidate(m_workspace->m_space, layoutTarget());\n                if (CAND)\n                    candidate = CAND->window();\n            }\n        }\n\n        Log::logger->log(Log::DEBUG, \"On closed window, new focused candidate is {}\", candidate);\n\n        if (candidate != Desktop::focusState()->window() && candidate) {\n            if (candidate == nextInGroup)\n                Desktop::focusState()->rawWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE);\n            else\n                Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE);\n\n            if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE)\n                g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE);\n        }\n\n        if (!candidate && m_workspace && m_workspace->getWindows() == 0)\n            g_pInputManager->refocus();\n\n        g_pInputManager->sendMotionEventsToFocused();\n\n        // CWindow::onUnmap will remove this window's active status, but we can't really do it above.\n        if (m_self.lock() == Desktop::focusState()->window() || !Desktop::focusState()->window()) {\n            g_pEventManager->postEvent(SHyprIPCEvent{\"activewindow\", \",\"});\n            g_pEventManager->postEvent(SHyprIPCEvent{\"activewindowv2\", \"\"});\n\n            Event::bus()->m_events.window.active.emit(m_self.lock(), FOCUS_REASON_OTHER);\n        }\n    } else {\n        Log::logger->log(Log::DEBUG, \"Unmapped was not focused, ignoring a refocus.\");\n    }\n\n    m_fadingOut = true;\n\n    g_pCompositor->addToFadingOutSafe(m_self.lock());\n\n    if (!m_X11DoesntWantBorders)                                            // don't animate out if they weren't animated in.\n        *m_realPosition = m_realPosition->value() + Vector2D(0.01f, 0.01f); // it has to be animated, otherwise CesktopAnimationManager will ignore it\n\n    // anims\n    g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n    // recheck idle inhibitors\n    g_pInputManager->recheckIdleInhibitorStatus();\n\n    // force report all sizes (QT sometimes has an issue with this)\n    if (m_workspace)\n        m_workspace->forceReportSizesToWindows();\n\n    // update lastwindow after focus\n    onUnmap();\n}\n\nvoid CWindow::commitWindow() {\n    if (!m_isX11 && m_xdgSurface->m_initialCommit) {\n        // try to calculate static rules already for any floats\n        m_ruleApplicator->readStaticRules(true);\n\n        const Vector2D predSize = !m_ruleApplicator->static_.floating.value_or(false) // no float rule\n                && !m_isFloating                                                      // not floating\n                && !parent()                                                          // no parents\n                && !g_pXWaylandManager->shouldBeFloated(m_self.lock(), true)          // should not be floated\n            ?\n            g_layoutManager->predictSizeForNewTiledTarget().value_or(Vector2D{}) :\n            Vector2D{};\n\n        Log::logger->log(Log::DEBUG, \"Layout predicts size {} for {}\", predSize, m_self.lock());\n\n        m_xdgSurface->m_toplevel->setSize(predSize);\n        return;\n    }\n\n    if (!m_isMapped || isHidden())\n        return;\n\n    if (m_isX11)\n        m_reportedSize = m_pendingReportedSize;\n\n    if (!m_isX11 && !isFullscreen() && m_isFloating) {\n        const auto MINSIZE = m_xdgSurface->m_toplevel->layoutMinSize();\n        const auto MAXSIZE = m_xdgSurface->m_toplevel->layoutMaxSize();\n\n        if (clampWindowSize(MINSIZE, MAXSIZE > Vector2D{1, 1} ? std::optional<Vector2D>{MAXSIZE} : std::nullopt))\n            g_pHyprRenderer->damageWindow(m_self.lock());\n    }\n\n    if (!m_workspace->m_visible)\n        return;\n\n    const auto PMONITOR = m_monitor.lock();\n\n    g_pHyprRenderer->damageSurface(wlSurface()->resource(), m_realPosition->goal().x, m_realPosition->goal().y, m_isX11 ? 1.0 / m_X11SurfaceScaledBy : 1.0);\n\n    if (!m_isX11) {\n        m_subsurfaceHead->recheckDamageForSubsurfaces();\n        m_popupHead->recheckTree();\n    }\n\n    // tearing: if solitary, redraw it. This still might be a single surface window\n    if (PMONITOR && PMONITOR->m_solitaryClient.lock() == m_self.lock() && canBeTorn() && PMONITOR->m_tearingState.canTear && wlSurface()->resource()->m_current.texture) {\n        CRegion damageBox{wlSurface()->resource()->m_current.accumulateBufferDamage()};\n\n        if (!damageBox.empty()) {\n            if (PMONITOR->m_tearingState.busy) {\n                PMONITOR->m_tearingState.frameScheduledWhileBusy = true;\n            } else {\n                PMONITOR->m_tearingState.nextRenderTorn = true;\n                g_pHyprRenderer->renderMonitor(PMONITOR);\n            }\n        }\n    }\n}\n\nvoid CWindow::destroyWindow() {\n    Log::logger->log(Log::DEBUG, \"{:c} destroyed, queueing.\", m_self.lock());\n\n    if (m_self.lock() == Desktop::focusState()->window()) {\n        Desktop::focusState()->window().reset();\n        Desktop::focusState()->surface().reset();\n    }\n\n    wlSurface()->unassign();\n\n    m_listeners = {};\n\n    g_layoutManager->removeTarget(m_target);\n\n    m_readyToDelete = true;\n\n    m_xdgSurface.reset();\n\n    m_listeners.unmap.reset();\n    m_listeners.destroy.reset();\n    m_listeners.map.reset();\n    m_listeners.commit.reset();\n\n    if (!m_fadingOut) {\n        Log::logger->log(Log::DEBUG, \"Unmapped {} removed instantly\", m_self.lock());\n        g_pCompositor->removeWindowFromVectorSafe(m_self.lock()); // most likely X11 unmanaged or sumn\n    }\n}\n\nvoid CWindow::activateX11() {\n    Log::logger->log(Log::DEBUG, \"X11 Activate request for window {}\", m_self.lock());\n\n    if (isX11OverrideRedirect()) {\n\n        Log::logger->log(Log::DEBUG, \"Unmanaged X11 {} requests activate\", m_self.lock());\n\n        if (Desktop::focusState()->window() && Desktop::focusState()->window()->getPID() != getPID())\n            return;\n\n        if (!m_xwaylandSurface->wantsFocus())\n            return;\n\n        Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);\n        return;\n    }\n\n    if (m_self.lock() == Desktop::focusState()->window() || (m_suppressedEvents & Desktop::View::SUPPRESS_ACTIVATE))\n        return;\n\n    activate();\n}\n\nvoid CWindow::unmanagedSetGeometry() {\n    if (!m_isMapped || !m_xwaylandSurface || !m_xwaylandSurface->m_overrideRedirect)\n        return;\n\n    const auto POS = m_realPosition->goal();\n    const auto SIZ = m_realSize->goal();\n\n    if (m_xwaylandSurface->m_geometry.size() > Vector2D{1, 1})\n        setHidden(false);\n    else\n        setHidden(true);\n\n    if (isFullscreen() || !m_isFloating) {\n        sendWindowSize(true);\n        g_pHyprRenderer->damageWindow(m_self.lock());\n        return;\n    }\n\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    const auto  LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(m_xwaylandSurface->m_geometry.pos());\n\n    const auto  PMONITOR       = m_monitor.lock();\n    const auto  XWLSCALE       = (*PXWLFORCESCALEZERO && PMONITOR) ? PMONITOR->m_scale : 1.0;\n    const auto  LOGICALGEOSIZE = m_xwaylandSurface->m_geometry.size() / XWLSCALE;\n\n    if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 ||\n        abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) {\n        Log::logger->log(Log::DEBUG, \"Unmanaged window {} requests geometry update to {:j} {:j}\", m_self.lock(), LOGICALPOS, m_xwaylandSurface->m_geometry.size());\n\n        g_pHyprRenderer->damageWindow(m_self.lock());\n        m_realPosition->setValueAndWarp(Vector2D(LOGICALPOS.x, LOGICALPOS.y));\n\n        if (abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 || abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2)\n            m_realSize->setValueAndWarp(LOGICALGEOSIZE);\n\n        m_position = m_realPosition->goal();\n        m_size     = m_realSize->goal();\n\n        m_workspace = g_pCompositor->getMonitorFromVector(m_realPosition->value() + m_realSize->value() / 2.f)->m_activeWorkspace;\n\n        g_pCompositor->changeWindowZOrder(m_self.lock(), true);\n        updateWindowDecos();\n        g_pHyprRenderer->damageWindow(m_self.lock());\n\n        m_reportedPosition    = m_realPosition->goal();\n        m_pendingReportedSize = m_realSize->goal();\n    }\n}\n\nstd::optional<Vector2D> CWindow::minSize() {\n    // first check for overrides\n    if (m_ruleApplicator->minSize().hasValue())\n        return m_ruleApplicator->minSize().value();\n\n    // then check if we have any proto overrides\n    bool hasSizeHints = m_xwaylandSurface ? m_xwaylandSurface->m_sizeHints : false;\n    bool hasTopLevel  = m_xdgSurface ? m_xdgSurface->m_toplevel : false;\n    if ((m_isX11 && !hasSizeHints) || (!m_isX11 && !hasTopLevel))\n        return std::nullopt;\n\n    Vector2D minSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->min_width, m_xwaylandSurface->m_sizeHints->min_height) : m_xdgSurface->m_toplevel->layoutMinSize();\n\n    minSize = minSize.clamp({1, 1});\n\n    return minSize;\n}\n\nstd::optional<Vector2D> CWindow::maxSize() {\n    // first check for overrides\n    if (m_ruleApplicator->maxSize().hasValue())\n        return m_ruleApplicator->maxSize().value();\n\n    // then check if we have any proto overrides\n    if (((m_isX11 && !m_xwaylandSurface->m_sizeHints) || (!m_isX11 && (!m_xdgSurface || !m_xdgSurface->m_toplevel)) || m_ruleApplicator->noMaxSize().valueOrDefault()))\n        return std::nullopt;\n\n    constexpr const double NO_MAX_SIZE_LIMIT = std::numeric_limits<double>::max();\n\n    Vector2D maxSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->max_width, m_xwaylandSurface->m_sizeHints->max_height) : m_xdgSurface->m_toplevel->layoutMaxSize();\n\n    if (maxSize.x < 5)\n        maxSize.x = NO_MAX_SIZE_LIMIT;\n    if (maxSize.y < 5)\n        maxSize.y = NO_MAX_SIZE_LIMIT;\n\n    return maxSize;\n}\n\nSP<Layout::ITarget> CWindow::layoutTarget() {\n    return m_group ? m_group->m_target : m_target;\n}\n\nbool CWindow::canBeGroupedInto(SP<CGroup> group) {\n    if (!group)\n        return false;\n\n    if (isX11OverrideRedirect())\n        return false;\n\n    static auto ALLOWGROUPMERGE       = CConfigValue<Hyprlang::INT>(\"group:merge_groups_on_drag\");\n    bool        isGroup               = m_group;\n    bool        disallowDragIntoGroup = g_layoutManager->dragController()->wasDraggingWindow() && isGroup && !sc<bool>(*ALLOWGROUPMERGE);\n    return !g_pKeybindManager->m_groupsLocked           // global group lock disengaged\n        && ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or\n            || (!group->locked()                        //      target unlocked\n                && !(m_group && m_group->locked())))    //      source unlocked or isn't group\n        && !(m_groupRules & GROUP_DENY)                 // source is not denied entry\n        && !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window\n        && !disallowDragIntoGroup;                      // config allows groups to be merged\n}\n"
  },
  {
    "path": "src/desktop/view/Window.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <string>\n#include <optional>\n\n#include \"View.hpp\"\n#include \"../../config/ConfigDataValues.hpp\"\n#include \"../../helpers/AnimatedVariable.hpp\"\n#include \"../../helpers/TagKeeper.hpp\"\n#include \"../../macros.hpp\"\n#include \"../../managers/XWaylandManager.hpp\"\n#include \"../../render/decorations/IHyprWindowDecoration.hpp\"\n#include \"../../render/Transformer.hpp\"\n#include \"../DesktopTypes.hpp\"\n#include \"Popup.hpp\"\n#include \"Subsurface.hpp\"\n#include \"WLSurface.hpp\"\n#include \"../Workspace.hpp\"\n#include \"../rule/windowRule/WindowRuleApplicator.hpp\"\n#include \"../../protocols/types/ContentType.hpp\"\n#include \"../../render/Framebuffer.hpp\"\n\nclass CXDGSurfaceResource;\nclass CXWaylandSurface;\nstruct SWorkspaceRule;\n\nclass IWindowTransformer;\n\nnamespace Layout {\n    class ITarget;\n    class CWindowTarget;\n}\n\nnamespace Desktop {\n    enum eFocusReason : uint8_t;\n}\n\nnamespace Desktop::View {\n\n    class CGroup;\n\n    enum eGroupRules : uint8_t {\n        // effective only during first map, except for _ALWAYS variant\n        GROUP_NONE        = 0,\n        GROUP_SET         = 1 << 0, // Open as new group or add to focused group\n        GROUP_SET_ALWAYS  = 1 << 1,\n        GROUP_BARRED      = 1 << 2, // Don't insert to focused group.\n        GROUP_LOCK        = 1 << 3, // Lock m_sGroupData.lock\n        GROUP_LOCK_ALWAYS = 1 << 4,\n        GROUP_INVADE      = 1 << 5, // Force enter a group, event if lock is engaged\n        GROUP_OVERRIDE    = 1 << 6, // Override other rules\n        GROUP_DENY        = 1 << 7, // deny\n    };\n\n    enum eGetWindowProperties : uint8_t {\n        WINDOW_ONLY              = 0,\n        RESERVED_EXTENTS         = 1 << 0,\n        INPUT_EXTENTS            = 1 << 1,\n        FULL_EXTENTS             = 1 << 2,\n        FLOATING_ONLY            = 1 << 3,\n        ALLOW_FLOATING           = 1 << 4,\n        USE_PROP_TILED           = 1 << 5,\n        SKIP_FULLSCREEN_PRIORITY = 1 << 6,\n        FOCUS_PRIORITY           = 1 << 7,\n    };\n\n    enum eSuppressEvents : uint8_t {\n        SUPPRESS_NONE               = 0,\n        SUPPRESS_FULLSCREEN         = 1 << 0,\n        SUPPRESS_MAXIMIZE           = 1 << 1,\n        SUPPRESS_ACTIVATE           = 1 << 2,\n        SUPPRESS_ACTIVATE_FOCUSONLY = 1 << 3,\n        SUPPRESS_FULLSCREEN_OUTPUT  = 1 << 4,\n    };\n\n    struct SWindowActiveEvent {\n        PHLWINDOW    window = nullptr;\n        eFocusReason reason = sc<eFocusReason>(0) /* unknown */;\n    };\n\n    struct SInitialWorkspaceToken {\n        PHLWINDOWREF primaryOwner;\n        std::string  workspace;\n    };\n\n    struct SFullscreenState {\n        eFullscreenMode internal = FSMODE_NONE;\n        eFullscreenMode client   = FSMODE_NONE;\n    };\n\n    class CWindow : public IView {\n      public:\n        static PHLWINDOW create(SP<CXDGSurfaceResource>);\n        static PHLWINDOW create(SP<CXWaylandSurface>);\n        static PHLWINDOW fromView(SP<IView>);\n\n      private:\n        CWindow(SP<CXDGSurfaceResource> resource);\n        CWindow(SP<CXWaylandSurface> surface);\n\n      public:\n        virtual ~CWindow();\n\n        virtual eViewType           type() const;\n        virtual bool                visible() const;\n        virtual std::optional<CBox> logicalBox() const;\n        virtual bool                desktopComponent() const;\n        virtual std::optional<CBox> surfaceLogicalBox() const;\n\n        struct {\n            CSignalT<> destroy;\n            CSignalT<> unmap;\n            CSignalT<> hide;\n            CSignalT<> resize;\n            CSignalT<> monitorChanged;\n        } m_events;\n\n        WP<CXDGSurfaceResource> m_xdgSurface;\n        WP<CXWaylandSurface>    m_xwaylandSurface;\n\n        SP<Layout::ITarget>     m_target;\n\n        // this is the position and size of the \"bounding box\"\n        Vector2D m_position = Vector2D(0, 0);\n        Vector2D m_size     = Vector2D(0, 0);\n\n        // this is the real position and size used to draw the thing\n        PHLANIMVAR<Vector2D> m_realPosition;\n        PHLANIMVAR<Vector2D> m_realSize;\n\n        // for not spamming the protocols\n        Vector2D                                     m_reportedPosition;\n        Vector2D                                     m_reportedSize;\n        Vector2D                                     m_pendingReportedSize;\n        std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck;\n        std::vector<std::pair<uint32_t, Vector2D>>   m_pendingSizeAcks;\n\n        // for floating window offset in workspace animations\n        Vector2D m_floatingOffset = Vector2D(0, 0);\n\n        // for recovering relative cursor position\n        Vector2D         m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1);\n\n        bool             m_firstMap        = false; // for layouts\n        bool             m_isFloating      = false;\n        SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE};\n        std::string      m_title           = \"\";\n        std::string      m_class           = \"\";\n        std::string      m_initialTitle    = \"\";\n        std::string      m_initialClass    = \"\";\n        PHLWORKSPACE     m_workspace;\n        PHLMONITORREF    m_monitor, m_prevMonitor;\n\n        bool             m_isMapped = false;\n\n        bool             m_requestsFloat = false;\n\n        // This is for fullscreen apps\n        bool m_createdOverFullscreen = false;\n\n        // XWayland stuff\n        bool  m_isX11                = false;\n        bool  m_X11DoesntWantBorders = false;\n        bool  m_X11ShouldntFocus     = false;\n        float m_X11SurfaceScaledBy   = 1.f;\n        //\n\n        // For nofocus\n        bool m_noInitialFocus = false;\n\n        // Fullscreen and Maximize\n        bool      m_wantsInitialFullscreen        = false;\n        MONITORID m_wantsInitialFullscreenMonitor = MONITOR_INVALID;\n\n        // bitfield suppressEvents\n        uint64_t m_suppressedEvents = SUPPRESS_NONE;\n\n        // desktop components\n        SP<Desktop::View::CSubsurface> m_subsurfaceHead;\n        SP<Desktop::View::CPopup>      m_popupHead;\n\n        // Animated border\n        CGradientValueData m_realBorderColor         = {0};\n        CGradientValueData m_realBorderColorPrevious = {0};\n        PHLANIMVAR<float>  m_borderFadeAnimationProgress;\n        PHLANIMVAR<float>  m_borderAngleAnimationProgress;\n\n        // Fade in-out\n        PHLANIMVAR<float> m_alpha;\n        bool              m_fadingOut     = false;\n        bool              m_readyToDelete = false;\n        Vector2D          m_originalClosedPos;  // these will be used for calculations later on in\n        Vector2D          m_originalClosedSize; // drawing the closing animations\n        SBoxExtents       m_originalClosedExtents;\n        bool              m_animatingIn = false;\n\n        // For pinned (sticky) windows\n        bool m_pinned = false;\n\n        // For preserving pinned state when fullscreening a pinned window\n        bool m_pinFullscreened = false;\n\n        // urgency hint\n        bool m_isUrgent = false;\n\n        // for proper cycling. While cycling we can't just move the pointers, so we need to keep track of the last cycled window.\n        PHLWINDOWREF m_lastCycledWindow;\n\n        // Window decorations\n        // TODO: make this a SP.\n        std::vector<UP<IHyprWindowDecoration>> m_windowDecorations;\n        std::vector<IHyprWindowDecoration*>    m_decosToRemove;\n\n        // Special render data, rules, etc\n        UP<Desktop::Rule::CWindowRuleApplicator> m_ruleApplicator;\n\n        // Transformers\n        std::vector<UP<IWindowTransformer>> m_transformers;\n\n        // for alpha\n        PHLANIMVAR<float> m_activeInactiveAlpha;\n        PHLANIMVAR<float> m_movingFromWorkspaceAlpha;\n\n        // animated shadow color\n        PHLANIMVAR<CHyprColor> m_realShadowColor;\n\n        // animated tint\n        PHLANIMVAR<float> m_dimPercent;\n\n        // animate moving to an invisible workspace\n        int               m_monitorMovedFrom = -1; // -1 means not moving\n        PHLANIMVAR<float> m_movingToWorkspaceAlpha;\n\n        // swallowing\n        PHLWINDOWREF m_swallowed;\n        bool         m_currentlySwallowed = false;\n        bool         m_groupSwallowed     = false;\n\n        // for toplevel monitor events\n        MONITORID m_lastSurfaceMonitorID = -1;\n\n        // initial token. Will be unregistered on workspace change or timeout of 2 minutes\n        std::string m_initialWorkspaceToken = \"\";\n\n        // for groups\n        SP<CGroup> m_group;\n        uint16_t   m_groupRules = Desktop::View::GROUP_NONE;\n\n        bool       m_tearingHint = false;\n\n        // Stable ID for ext_foreign_toplevel_list\n        const uint64_t m_stableID = 0x2137;\n\n        // snapshots\n        SP<IFramebuffer> m_snapshotFB;\n\n        // ANR\n        PHLANIMVAR<float> m_notRespondingTint;\n\n        // For the noclosefor windowrule\n        Time::steady_tp m_closeableSince = Time::steadyNow();\n\n        // For the list lookup\n        bool operator==(const CWindow& rhs) const {\n            return m_xdgSurface == rhs.m_xdgSurface && m_xwaylandSurface == rhs.m_xwaylandSurface && m_position == rhs.m_position && m_size == rhs.m_size &&\n                m_fadingOut == rhs.m_fadingOut;\n        }\n\n        // methods\n        CBox                       getFullWindowBoundingBox() const;\n        SBoxExtents                getFullWindowExtents() const;\n        CBox                       getWindowBoxUnified(uint64_t props);\n        SBoxExtents                getWindowExtentsUnified(uint64_t props);\n        CBox                       getWindowIdealBoundingBoxIgnoreReserved();\n        void                       addWindowDeco(UP<IHyprWindowDecoration> deco);\n        void                       updateWindowDecos();\n        void                       removeWindowDeco(IHyprWindowDecoration* deco);\n        void                       uncacheWindowDecos();\n        bool                       checkInputOnDecos(const eInputType, const Vector2D&, std::any = {});\n        pid_t                      getPID();\n        IHyprWindowDecoration*     getDecorationByType(eDecorationType);\n        void                       updateToplevel();\n        void                       updateSurfaceScaleTransformDetails(bool force = false);\n        void                       moveToWorkspace(PHLWORKSPACE);\n        PHLWINDOW                  x11TransientFor();\n        void                       onUnmap();\n        void                       onMap();\n        void                       setHidden(bool hidden);\n        bool                       isHidden();\n        void                       updateDecorationValues();\n        SBoxExtents                getFullWindowReservedArea();\n        Vector2D                   middle();\n        bool                       opaque();\n        float                      rounding();\n        float                      roundingPower();\n        bool                       canBeTorn();\n        void                       setSuspended(bool suspend);\n        bool                       visibleOnMonitor(PHLMONITOR pMonitor);\n        WORKSPACEID                workspaceID();\n        MONITORID                  monitorID();\n        bool                       onSpecialWorkspace();\n        void                       activate(bool force = false);\n        int                        surfacesCount();\n        bool                       clampWindowSize(const std::optional<Vector2D> minSize, const std::optional<Vector2D> maxSize);\n        bool                       isFullscreen();\n        bool                       isEffectiveInternalFSMode(const eFullscreenMode) const;\n        int                        getRealBorderSize() const;\n        float                      getScrollMouse();\n        float                      getScrollTouchpad();\n        bool                       isScrollMouseOverridden();\n        bool                       isScrollTouchpadOverridden();\n        void                       updateWindowData();\n        void                       updateWindowData(const SWorkspaceRule&);\n        void                       onBorderAngleAnimEnd(WP<Hyprutils::Animation::CBaseAnimatedVariable> pav);\n        bool                       isInCurvedCorner(double x, double y);\n        bool                       hasPopupAt(const Vector2D& pos);\n        int                        popupsCount();\n        void                       setAnimationsToMove();\n        void                       onWorkspaceAnimUpdate();\n        void                       onFocusAnimUpdate();\n        void                       onUpdateState();\n        void                       onUpdateMeta();\n        void                       onX11ConfigureRequest(CBox box);\n        void                       onResourceChangeX11();\n        std::string                fetchTitle();\n        std::string                fetchClass();\n        void                       warpCursor(bool force = false);\n        PHLWINDOW                  getSwallower();\n        bool                       isX11OverrideRedirect();\n        bool                       isModal();\n        Vector2D                   realToReportSize();\n        Vector2D                   realToReportPosition();\n        Vector2D                   xwaylandSizeToReal(Vector2D size);\n        Vector2D                   xwaylandPositionToReal(Vector2D size);\n        void                       updateX11SurfaceScale();\n        void                       sendWindowSize(bool force = false);\n        NContentType::eContentType getContentType();\n        void                       setContentType(NContentType::eContentType contentType);\n        void                       deactivateGroupMembers();\n        bool                       isNotResponding();\n        std::optional<std::string> xdgTag();\n        std::optional<std::string> xdgDescription();\n        PHLWINDOW                  parent();\n        bool                       priorityFocus();\n        SP<CWLSurfaceResource>     getSolitaryResource();\n        Vector2D                   getReportedSize();\n        std::optional<Vector2D>    calculateExpression(const std::string& s);\n        std::optional<Vector2D>    minSize();\n        std::optional<Vector2D>    maxSize();\n        SP<Layout::ITarget>        layoutTarget();\n        bool                       canBeGroupedInto(SP<CGroup> group);\n\n        CBox                       getWindowMainSurfaceBox() const {\n            return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};\n        }\n\n        // listeners\n        void onAck(uint32_t serial);\n\n        //\n        std::unordered_map<std::string, std::string> getEnv();\n\n        //\n        PHLWINDOWREF m_self;\n\n        // make private once we move listeners to inside CWindow\n        struct {\n            CHyprSignalListener map;\n            CHyprSignalListener ack;\n            CHyprSignalListener unmap;\n            CHyprSignalListener commit;\n            CHyprSignalListener destroy;\n            CHyprSignalListener activate;\n            CHyprSignalListener configureRequest;\n            CHyprSignalListener setGeometry;\n            CHyprSignalListener updateState;\n            CHyprSignalListener updateMetadata;\n            CHyprSignalListener resourceChange;\n        } m_listeners;\n\n      private:\n        std::optional<double> calculateSingleExpr(const std::string& s);\n        void                  mapWindow();\n        void                  unmapWindow();\n        void                  commitWindow();\n        void                  destroyWindow();\n        void                  activateX11();\n        void                  unmanagedSetGeometry();\n\n        // For hidden windows and stuff\n        bool        m_hidden        = false;\n        bool        m_suspended     = false;\n        WORKSPACEID m_lastWorkspace = WORKSPACE_INVALID;\n    };\n\n    inline bool valid(PHLWINDOW w) {\n        return w.get();\n    }\n\n    inline bool valid(PHLWINDOWREF w) {\n        return !w.expired();\n    }\n\n    inline bool validMapped(PHLWINDOW w) {\n        if (!valid(w))\n            return false;\n        return w->m_isMapped;\n    }\n\n    inline bool validMapped(PHLWINDOWREF w) {\n        if (!valid(w))\n            return false;\n        return w->m_isMapped;\n    }\n}\n\n/**\n    format specification\n    - 'x', only address, equivalent of (uintpr_t)CWindow*\n    - 'm', with monitor id\n    - 'w', with workspace id\n    - 'c', with application class\n*/\n\ntemplate <typename CharT>\nstruct std::formatter<PHLWINDOW, CharT> : std::formatter<CharT> {\n    bool formatAddressOnly = false;\n    bool formatWorkspace   = false;\n    bool formatMonitor     = false;\n    bool formatClass       = false;\n    FORMAT_PARSE(                           //\n        FORMAT_FLAG('x', formatAddressOnly) //\n        FORMAT_FLAG('m', formatMonitor)     //\n        FORMAT_FLAG('w', formatWorkspace)   //\n        FORMAT_FLAG('c', formatClass),\n        PHLWINDOW)\n\n    template <typename FormatContext>\n    auto format(PHLWINDOW const& w, FormatContext& ctx) const {\n        auto&& out = ctx.out();\n        if (formatAddressOnly)\n            return std::format_to(out, \"{:x}\", rc<uintptr_t>(w.get()));\n        if (!w)\n            return std::format_to(out, \"[Window nullptr]\");\n\n        std::format_to(out, \"[\");\n        std::format_to(out, \"Window {:x}: title: \\\"{}\\\"\", rc<uintptr_t>(w.get()), w->m_title);\n        if (formatWorkspace)\n            std::format_to(out, \", workspace: {}\", w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID);\n        if (formatMonitor)\n            std::format_to(out, \", monitor: {}\", w->monitorID());\n        if (formatClass)\n            std::format_to(out, \", class: {}\", w->m_class);\n        return std::format_to(out, \"]\");\n    }\n};\n"
  },
  {
    "path": "src/devices/IHID.cpp",
    "content": "#include \"IHID.hpp\"\n\neHIDType IHID::getType() {\n    return HID_TYPE_UNKNOWN;\n}\n"
  },
  {
    "path": "src/devices/IHID.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <string>\n#include \"../helpers/signal/Signal.hpp\"\n\nenum eHIDCapabilityType : uint8_t {\n    HID_INPUT_CAPABILITY_KEYBOARD = (1 << 0),\n    HID_INPUT_CAPABILITY_POINTER  = (1 << 1),\n    HID_INPUT_CAPABILITY_TOUCH    = (1 << 2),\n    HID_INPUT_CAPABILITY_TABLET   = (1 << 3),\n};\n\nenum eHIDType : uint8_t {\n    HID_TYPE_UNKNOWN = 0,\n    HID_TYPE_POINTER,\n    HID_TYPE_KEYBOARD,\n    HID_TYPE_TOUCH,\n    HID_TYPE_TABLET,\n    HID_TYPE_TABLET_TOOL,\n    HID_TYPE_TABLET_PAD,\n};\n\n/*\n    Base class for a HID device.\n    This could be a keyboard, a mouse, or a touchscreen.\n*/\nclass IHID {\n  public:\n    virtual ~IHID() = default;\n\n    virtual uint32_t getCapabilities() = 0;\n    virtual eHIDType getType();\n\n    struct {\n        CSignalT<> destroy;\n    } m_events;\n\n    std::string m_deviceName;\n    std::string m_hlName;\n};\n"
  },
  {
    "path": "src/devices/IKeyboard.cpp",
    "content": "#include \"IKeyboard.hpp\"\n#include \"../defines.hpp\"\n#include \"../helpers/varlist/VarList.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include <sys/mman.h>\n#include <aquamarine/input/Input.hpp>\n#include <cstring>\n\nusing namespace Hyprutils::OS;\n\n#define LED_COUNT 3\n\nconstexpr static std::array<const char*, 8> MODNAMES = {\n    XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CAPS, XKB_MOD_NAME_CTRL, XKB_MOD_NAME_ALT, XKB_MOD_NAME_NUM, \"Mod3\", XKB_MOD_NAME_LOGO, \"Mod5\",\n};\n\nconstexpr static std::array<const char*, 3> LEDNAMES = {XKB_LED_NAME_NUM, XKB_LED_NAME_CAPS, XKB_LED_NAME_SCROLL};\n\n//\nuint32_t IKeyboard::getCapabilities() {\n    return HID_INPUT_CAPABILITY_KEYBOARD;\n}\n\neHIDType IKeyboard::getType() {\n    return HID_TYPE_KEYBOARD;\n}\n\nIKeyboard::~IKeyboard() {\n    m_events.destroy.emit();\n\n    clearManuallyAllocd();\n}\n\nvoid IKeyboard::clearManuallyAllocd() {\n    if (m_xkbStaticState)\n        xkb_state_unref(m_xkbStaticState);\n\n    if (m_xkbState)\n        xkb_state_unref(m_xkbState);\n\n    if (m_xkbKeymap)\n        xkb_keymap_unref(m_xkbKeymap);\n\n    if (m_xkbSymState)\n        xkb_state_unref(m_xkbSymState);\n\n    m_xkbSymState    = nullptr;\n    m_xkbKeymap      = nullptr;\n    m_xkbState       = nullptr;\n    m_xkbStaticState = nullptr;\n    m_xkbKeymapFD.reset();\n    m_xkbKeymapV1FD.reset();\n}\n\nvoid IKeyboard::setKeymap(const SStringRuleNames& rules) {\n    if (m_keymapOverridden) {\n        Log::logger->log(Log::DEBUG, \"Ignoring setKeymap: keymap is overridden\");\n        return;\n    }\n\n    m_currentRules          = rules;\n    xkb_rule_names XKBRULES = {\n        .rules   = rules.rules.c_str(),\n        .model   = rules.model.c_str(),\n        .layout  = rules.layout.c_str(),\n        .variant = rules.variant.c_str(),\n        .options = rules.options.c_str(),\n    };\n\n    const auto CONTEXT = xkb_context_new(XKB_CONTEXT_NO_FLAGS);\n\n    if (!CONTEXT) {\n        Log::logger->log(Log::ERR, \"setKeymap: CONTEXT null??\");\n        return;\n    }\n\n    clearManuallyAllocd();\n\n    Log::logger->log(Log::DEBUG, \"Attempting to create a keymap for layout {} with variant {} (rules: {}, model: {}, options: {})\", rules.layout, rules.variant, rules.rules,\n                     rules.model, rules.options);\n\n    if (!m_xkbFilePath.empty()) {\n        auto path = absolutePath(m_xkbFilePath, g_pConfigManager->m_configCurrentPath);\n\n        if (FILE* const KEYMAPFILE = fopen(path.c_str(), \"r\"); !KEYMAPFILE)\n            Log::logger->log(Log::ERR, \"Cannot open input:kb_file= file for reading\");\n        else {\n            m_xkbKeymap = xkb_keymap_new_from_file(CONTEXT, KEYMAPFILE, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n            fclose(KEYMAPFILE);\n        }\n    }\n\n    if (!m_xkbKeymap)\n        m_xkbKeymap = xkb_keymap_new_from_names2(CONTEXT, &XKBRULES, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n\n    if (!m_xkbKeymap) {\n        g_pConfigManager->addParseError(\"Invalid keyboard layout passed. ( rules: \" + rules.rules + \", model: \" + rules.model + \", variant: \" + rules.variant +\n                                        \", options: \" + rules.options + \", layout: \" + rules.layout + \" )\");\n\n        Log::logger->log(Log::ERR, \"Keyboard layout {} with variant {} (rules: {}, model: {}, options: {}) couldn't have been loaded.\", rules.layout, rules.variant, rules.rules,\n                         rules.model, rules.options);\n        memset(&XKBRULES, 0, sizeof(XKBRULES));\n\n        m_currentRules.rules   = \"\";\n        m_currentRules.model   = \"\";\n        m_currentRules.variant = \"\";\n        m_currentRules.options = \"\";\n        m_currentRules.layout  = \"us\";\n\n        m_xkbKeymap = xkb_keymap_new_from_names2(CONTEXT, &XKBRULES, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n    }\n\n    updateXKBTranslationState(m_xkbKeymap);\n\n    const auto NUMLOCKON = g_pConfigManager->getDeviceInt(m_hlName, \"numlock_by_default\", \"input:numlock_by_default\");\n\n    if (NUMLOCKON == 1) {\n        // lock numlock\n        const auto IDX = xkb_map_mod_get_index(m_xkbKeymap, XKB_MOD_NAME_NUM);\n\n        if (IDX != XKB_MOD_INVALID)\n            m_modifiersState.locked |= sc<uint32_t>(1) << IDX;\n\n        // 0 to avoid mods getting stuck if depressed during reload\n        updateModifiers(0, 0, m_modifiersState.locked, m_modifiersState.group);\n    }\n\n    for (size_t i = 0; i < std::min(LEDNAMES.size(), m_ledIndexes.size()); ++i) {\n        m_ledIndexes[i] = xkb_map_led_get_index(m_xkbKeymap, LEDNAMES[i]);\n        Log::logger->log(Log::DEBUG, \"xkb: LED index {} (name {}) got index {}\", i, LEDNAMES[i], m_ledIndexes[i]);\n    }\n\n    for (size_t i = 0; i < std::min(MODNAMES.size(), m_modIndexes.size()); ++i) {\n        m_modIndexes[i] = xkb_map_mod_get_index(m_xkbKeymap, MODNAMES[i]);\n        Log::logger->log(Log::DEBUG, \"xkb: Mod index {} (name {}) got index {}\", i, MODNAMES[i], m_modIndexes[i]);\n    }\n\n    updateKeymapFD();\n\n    xkb_context_unref(CONTEXT);\n\n    g_pSeatManager->updateActiveKeyboardData();\n}\n\nvoid IKeyboard::updateKeymapFD() {\n    Log::logger->log(Log::DEBUG, \"Updating keymap fd for keyboard {}\", m_deviceName);\n\n    if (m_xkbKeymapFD.isValid())\n        m_xkbKeymapFD.reset();\n\n    if (m_xkbKeymapV1FD.isValid())\n        m_xkbKeymapV1FD.reset();\n\n    auto cKeymapStr   = xkb_keymap_get_as_string(m_xkbKeymap, XKB_KEYMAP_FORMAT_TEXT_V2);\n    m_xkbKeymapString = cKeymapStr;\n    free(cKeymapStr); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)\n    auto cKeymapV1Str   = xkb_keymap_get_as_string(m_xkbKeymap, XKB_KEYMAP_FORMAT_TEXT_V1);\n    m_xkbKeymapV1String = cKeymapV1Str;\n    free(cKeymapV1Str); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)\n\n    CFileDescriptor rw, ro, rwV1, roV1;\n    if (!allocateSHMFilePair(m_xkbKeymapString.length() + 1, rw, ro))\n        Log::logger->log(Log::ERR, \"IKeyboard: failed to allocate shm pair for the keymap\");\n    else if (!allocateSHMFilePair(m_xkbKeymapV1String.length() + 1, rwV1, roV1)) {\n        ro.reset();\n        rw.reset();\n        Log::logger->log(Log::ERR, \"IKeyboard: failed to allocate shm pair for keymap V1\");\n    } else {\n        auto keymapFDDest   = mmap(nullptr, m_xkbKeymapString.length() + 1, PROT_READ | PROT_WRITE, MAP_SHARED, rw.get(), 0);\n        auto keymapV1FDDest = mmap(nullptr, m_xkbKeymapV1String.length() + 1, PROT_READ | PROT_WRITE, MAP_SHARED, rwV1.get(), 0);\n        rw.reset();\n        rwV1.reset();\n\n        if (keymapFDDest == MAP_FAILED || keymapV1FDDest == MAP_FAILED) {\n            Log::logger->log(Log::ERR, \"IKeyboard: failed to mmap a shm pair for the keymap\");\n            ro.reset();\n            roV1.reset();\n        } else {\n            memcpy(keymapFDDest, m_xkbKeymapString.c_str(), m_xkbKeymapString.length());\n            munmap(keymapFDDest, m_xkbKeymapString.length() + 1);\n            m_xkbKeymapFD = std::move(ro);\n            memcpy(keymapV1FDDest, m_xkbKeymapV1String.c_str(), m_xkbKeymapV1String.length());\n            munmap(keymapV1FDDest, m_xkbKeymapV1String.length() + 1);\n            m_xkbKeymapV1FD = std::move(roV1);\n        }\n    }\n\n    Log::logger->log(Log::DEBUG, \"Updated keymap fd to {}, keymap V1 to: {}\", m_xkbKeymapFD.get(), m_xkbKeymapV1FD.get());\n}\n\nvoid IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) {\n\n    if (m_xkbStaticState)\n        xkb_state_unref(m_xkbStaticState);\n\n    if (m_xkbState)\n        xkb_state_unref(m_xkbState);\n\n    if (m_xkbSymState)\n        xkb_state_unref(m_xkbSymState);\n\n    m_xkbState       = nullptr;\n    m_xkbStaticState = nullptr;\n    m_xkbSymState    = nullptr;\n\n    if (keymap) {\n        Log::logger->log(Log::DEBUG, \"Updating keyboard {:x}'s translation state from a provided keymap\", rc<uintptr_t>(this));\n        m_xkbStaticState = xkb_state_new(keymap);\n        m_xkbState       = xkb_state_new(keymap);\n        m_xkbSymState    = xkb_state_new(keymap);\n        return;\n    }\n\n    const auto KEYMAP     = m_xkbKeymap;\n    const auto STATE      = m_xkbState;\n    const auto LAYOUTSNUM = xkb_keymap_num_layouts(KEYMAP);\n\n    const auto PCONTEXT = xkb_context_new(XKB_CONTEXT_NO_FLAGS);\n\n    for (uint32_t i = 0; i < LAYOUTSNUM; ++i) {\n        if (xkb_state_layout_index_is_active(STATE, i, XKB_STATE_LAYOUT_EFFECTIVE) == 1) {\n            Log::logger->log(Log::DEBUG, \"Updating keyboard {:x}'s translation state from an active index {}\", rc<uintptr_t>(this), i);\n\n            CVarList       keyboardLayouts(m_currentRules.layout, 0, ',');\n            CVarList       keyboardModels(m_currentRules.model, 0, ',');\n            CVarList       keyboardVariants(m_currentRules.variant, 0, ',');\n\n            xkb_rule_names rules = {.rules = \"\", .model = \"\", .layout = \"\", .variant = \"\", .options = \"\"};\n\n            std::string    layout, model, variant;\n            layout  = keyboardLayouts[i % keyboardLayouts.size()];\n            model   = keyboardModels[i % keyboardModels.size()];\n            variant = keyboardVariants[i % keyboardVariants.size()];\n\n            rules.layout  = layout.c_str();\n            rules.model   = model.c_str();\n            rules.variant = variant.c_str();\n\n            auto KEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n\n            if (!KEYMAP) {\n                Log::logger->log(Log::ERR, \"updateXKBTranslationState: keymap failed 1, fallback without model/variant\");\n                rules.model   = \"\";\n                rules.variant = \"\";\n                KEYMAP        = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n            }\n\n            if (!KEYMAP) {\n                Log::logger->log(Log::ERR, \"updateXKBTranslationState: keymap failed 2, fallback to us\");\n                rules.layout = \"us\";\n                KEYMAP       = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n            }\n\n            m_xkbState       = xkb_state_new(KEYMAP);\n            m_xkbStaticState = xkb_state_new(KEYMAP);\n            m_xkbSymState    = xkb_state_new(KEYMAP);\n\n            xkb_keymap_unref(KEYMAP);\n            xkb_context_unref(PCONTEXT);\n\n            return;\n        }\n    }\n\n    Log::logger->log(Log::DEBUG, \"Updating keyboard {:x}'s translation state from an unknown index\", rc<uintptr_t>(this));\n\n    xkb_rule_names rules = {\n        .rules   = m_currentRules.rules.c_str(),\n        .model   = m_currentRules.model.c_str(),\n        .layout  = m_currentRules.layout.c_str(),\n        .variant = m_currentRules.variant.c_str(),\n        .options = m_currentRules.options.c_str(),\n    };\n\n    const auto NEWKEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n\n    m_xkbState       = xkb_state_new(NEWKEYMAP);\n    m_xkbStaticState = xkb_state_new(NEWKEYMAP);\n    m_xkbSymState    = xkb_state_new(NEWKEYMAP);\n\n    xkb_keymap_unref(NEWKEYMAP);\n    xkb_context_unref(PCONTEXT);\n}\n\nstd::optional<xkb_layout_index_t> IKeyboard::getActiveLayoutIndex() {\n    const auto KEYMAP     = m_xkbKeymap;\n    const auto STATE      = m_xkbState;\n    const auto LAYOUTSNUM = xkb_keymap_num_layouts(KEYMAP);\n\n    for (xkb_layout_index_t i = 0; i < LAYOUTSNUM; ++i) {\n        if (xkb_state_layout_index_is_active(STATE, i, XKB_STATE_LAYOUT_EFFECTIVE) == 1)\n            return i;\n    }\n\n    return {};\n}\n\nstd::string IKeyboard::getActiveLayout() {\n    const auto KEYMAP     = m_xkbKeymap;\n    const auto STATE      = m_xkbState;\n    const auto LAYOUTSNUM = xkb_keymap_num_layouts(KEYMAP);\n\n    for (uint32_t i = 0; i < LAYOUTSNUM; ++i) {\n        if (xkb_state_layout_index_is_active(STATE, i, XKB_STATE_LAYOUT_EFFECTIVE) == 1) {\n            const auto LAYOUTNAME = xkb_keymap_layout_get_name(KEYMAP, i);\n\n            if (LAYOUTNAME)\n                return std::string(LAYOUTNAME);\n            return \"error\";\n        }\n    }\n\n    return \"none\";\n}\n\nstd::optional<uint32_t> IKeyboard::getLEDs() {\n    if (m_xkbState == nullptr)\n        return {};\n\n    uint32_t leds = 0;\n    for (uint32_t i = 0; i < std::min(sc<size_t>(LED_COUNT), m_ledIndexes.size()); ++i) {\n        if (xkb_state_led_index_is_active(m_xkbState, m_ledIndexes[i]))\n            leds |= (1 << i);\n    }\n\n    return leds;\n}\n\nvoid IKeyboard::updateLEDs() {\n    std::optional<uint32_t> leds = getLEDs();\n\n    if (!leds.has_value())\n        return;\n\n    updateLEDs(leds.value());\n}\n\nvoid IKeyboard::updateLEDs(uint32_t leds) {\n    if (!m_xkbState)\n        return;\n\n    if (isVirtual() && g_pInputManager->shouldIgnoreVirtualKeyboard(m_self.lock()))\n        return;\n\n    if (!aq())\n        return;\n\n    aq()->updateLEDs(leds);\n}\n\nuint32_t IKeyboard::getModifiers() {\n    uint32_t modMask = m_modifiersState.depressed | m_modifiersState.latched;\n    uint32_t mods    = 0;\n    for (size_t i = 0; i < m_modIndexes.size(); ++i) {\n        if (m_modIndexes[i] == XKB_MOD_INVALID)\n            continue;\n\n        if (!(modMask & (1 << m_modIndexes[i])))\n            continue;\n\n        mods |= (1 << i);\n    }\n\n    return mods;\n}\n\nvoid IKeyboard::updateModifiers(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {\n    if (!m_xkbState)\n        return;\n\n    xkb_state_update_mask(m_xkbState, depressed, latched, locked, 0, 0, group);\n\n    if (m_xkbSymState)\n        xkb_state_update_mask(m_xkbSymState, 0, 0, 0, 0, 0, group);\n\n    if (!updateModifiersState())\n        return;\n\n    m_keyboardEvents.modifiers.emit(SModifiersEvent{\n        .depressed = m_modifiersState.depressed,\n        .latched   = m_modifiersState.latched,\n        .locked    = m_modifiersState.locked,\n        .group     = m_modifiersState.group,\n    });\n\n    updateLEDs();\n}\n\nbool IKeyboard::updateModifiersState() {\n    if (!m_xkbState)\n        return false;\n\n    auto depressed = xkb_state_serialize_mods(m_xkbState, XKB_STATE_MODS_DEPRESSED);\n    auto latched   = xkb_state_serialize_mods(m_xkbState, XKB_STATE_MODS_LATCHED);\n    auto locked    = xkb_state_serialize_mods(m_xkbState, XKB_STATE_MODS_LOCKED);\n    auto group     = xkb_state_serialize_layout(m_xkbState, XKB_STATE_LAYOUT_EFFECTIVE);\n\n    if (depressed == m_modifiersState.depressed && latched == m_modifiersState.latched && locked == m_modifiersState.locked && group == m_modifiersState.group)\n        return false;\n\n    m_modifiersState.depressed = depressed;\n    m_modifiersState.latched   = latched;\n    m_modifiersState.locked    = locked;\n    m_modifiersState.group     = group;\n\n    return true;\n}\n\nvoid IKeyboard::updateXkbStateWithKey(uint32_t xkbKey, bool pressed) {\n    xkb_state_update_key(m_xkbState, xkbKey, pressed ? XKB_KEY_DOWN : XKB_KEY_UP);\n\n    if (updateModifiersState()) {\n        if (m_xkbSymState)\n            xkb_state_update_mask(m_xkbSymState, 0, 0, 0, 0, 0, m_modifiersState.group);\n\n        m_keyboardEvents.modifiers.emit(SModifiersEvent{\n            .depressed = m_modifiersState.depressed,\n            .latched   = m_modifiersState.latched,\n            .locked    = m_modifiersState.locked,\n            .group     = m_modifiersState.group,\n        });\n    }\n}\n\nbool IKeyboard::updatePressed(uint32_t key, bool pressed) {\n    const auto contains = getPressed(key);\n\n    if (contains && pressed)\n        return false;\n    if (!contains && !pressed)\n        return false;\n\n    if (contains)\n        std::erase(m_pressed, key);\n    else\n        m_pressed.emplace_back(key);\n\n    return true;\n}\n\nbool IKeyboard::getPressed(uint32_t key) {\n    return std::ranges::contains(m_pressed, key);\n}\n\nbool IKeyboard::shareStates() {\n    return m_shareStates;\n}\n\nvoid IKeyboard::setShareStatesAuto(bool shareStates) {\n    if (m_shareStatesAuto)\n        m_shareStates = shareStates;\n}\n"
  },
  {
    "path": "src/devices/IKeyboard.hpp",
    "content": "#pragma once\n\n#include \"IHID.hpp\"\n#include \"../macros.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\n#include <optional>\n#include <xkbcommon/xkbcommon.h>\n#include <hyprutils/os/FileDescriptor.hpp>\n\nAQUAMARINE_FORWARD(IKeyboard);\n\nenum eKeyboardModifiers {\n    HL_MODIFIER_SHIFT = (1 << 0),\n    HL_MODIFIER_CAPS  = (1 << 1),\n    HL_MODIFIER_CTRL  = (1 << 2),\n    HL_MODIFIER_ALT   = (1 << 3),\n    HL_MODIFIER_MOD2  = (1 << 4),\n    HL_MODIFIER_MOD3  = (1 << 5),\n    HL_MODIFIER_META  = (1 << 6),\n    HL_MODIFIER_MOD5  = (1 << 7),\n};\n\nclass IKeyboard : public IHID {\n  public:\n    virtual ~IKeyboard();\n    virtual uint32_t   getCapabilities();\n    virtual eHIDType   getType();\n    virtual bool       isVirtual() = 0;\n    virtual wl_client* getClient() {\n        return nullptr;\n    };\n    virtual SP<Aquamarine::IKeyboard> aq() = 0;\n\n    struct SKeyEvent {\n        uint32_t              timeMs     = 0;\n        uint32_t              keycode    = 0;\n        bool                  updateMods = false;\n        wl_keyboard_key_state state      = WL_KEYBOARD_KEY_STATE_PRESSED;\n    };\n\n    struct SKeymapEvent {\n        xkb_keymap* keymap = nullptr;\n    };\n\n    struct SModifiersEvent {\n        uint32_t depressed = 0;\n        uint32_t latched   = 0;\n        uint32_t locked    = 0;\n        uint32_t group     = 0;\n    };\n\n    struct {\n        CSignalT<SKeyEvent>       key;\n        CSignalT<SModifiersEvent> modifiers;\n        CSignalT<SKeymapEvent>    keymap;\n        CSignalT<>                repeatInfo;\n    } m_keyboardEvents;\n\n    struct SStringRuleNames {\n        std::string layout  = \"\";\n        std::string model   = \"\";\n        std::string variant = \"\";\n        std::string options = \"\";\n        std::string rules   = \"\";\n    };\n\n    void                              setKeymap(const SStringRuleNames& rules);\n    void                              updateXKBTranslationState(xkb_keymap* const keymap = nullptr);\n    std::optional<xkb_layout_index_t> getActiveLayoutIndex();\n    std::string                       getActiveLayout();\n    std::optional<uint32_t>           getLEDs();\n    void                              updateLEDs();\n    void                              updateLEDs(uint32_t leds);\n    uint32_t                          getModifiers();\n    void                              updateModifiers(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group);\n    bool                              updateModifiersState(); // rets whether changed\n    void                              updateXkbStateWithKey(uint32_t xkbKey, bool pressed);\n    void                              updateKeymapFD();\n    bool                              getPressed(uint32_t key);\n    bool                              shareStates();\n    void                              setShareStatesAuto(bool shareStates);\n\n    bool                              m_active     = false;\n    bool                              m_enabled    = true;\n    bool                              m_allowBinds = true;\n\n    // permission flag: whether this keyboard is allowed to be processed\n    bool m_allowed = true;\n\n    // if the keymap is overridden by the implementation,\n    // don't try to set keyboard rules anymore, to avoid overwriting the requested one.\n    // e.g. Virtual keyboards with custom maps.\n    bool               m_keymapOverridden = false;\n\n    xkb_layout_index_t m_activeLayout = 0;\n    xkb_state*         m_xkbState     = nullptr;\n\n    // never gets modifiers or layout changes sent, used for keybinds\n    xkb_state*  m_xkbStaticState = nullptr;\n    xkb_state*  m_xkbSymState    = nullptr; // same as static but gets layouts\n\n    xkb_keymap* m_xkbKeymap = nullptr;\n\n    struct {\n        uint32_t depressed = 0, latched = 0, locked = 0, group = 0;\n    } m_modifiersState;\n\n    std::array<xkb_led_index_t, 3> m_ledIndexes = {XKB_MOD_INVALID};\n    std::array<xkb_mod_index_t, 8> m_modIndexes = {XKB_MOD_INVALID};\n    uint32_t                       m_leds       = 0;\n\n    std::string                    m_xkbFilePath     = \"\";\n    std::string                    m_xkbKeymapString = \"\";\n    Hyprutils::OS::CFileDescriptor m_xkbKeymapFD;\n\n    std::string                    m_xkbKeymapV1String = \"\";\n    Hyprutils::OS::CFileDescriptor m_xkbKeymapV1FD;\n\n    SStringRuleNames               m_currentRules;\n    int                            m_repeatRate        = 0;\n    int                            m_repeatDelay       = 0;\n    int                            m_numlockOn         = -1;\n    bool                           m_resolveBindsBySym = false;\n\n    WP<IKeyboard>                  m_self;\n\n  private:\n    void                  clearManuallyAllocd();\n\n    std::vector<uint32_t> m_pressed;\n\n  protected:\n    bool updatePressed(uint32_t key, bool pressed);\n    bool m_shareStates     = true;\n    bool m_shareStatesAuto = true;\n};\n"
  },
  {
    "path": "src/devices/IPointer.cpp",
    "content": "#include \"IPointer.hpp\"\n\nuint32_t IPointer::getCapabilities() {\n    return HID_INPUT_CAPABILITY_POINTER;\n}\n\neHIDType IPointer::getType() {\n    return HID_TYPE_POINTER;\n}\n"
  },
  {
    "path": "src/devices/IPointer.hpp",
    "content": "#pragma once\n\n#include \"IHID.hpp\"\n#include \"../macros.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\nAQUAMARINE_FORWARD(IPointer);\n\n/*\n    Base class for a pointer.\n*/\nclass IPointer : public IHID {\n  public:\n    virtual uint32_t                 getCapabilities();\n    virtual eHIDType                 getType();\n    virtual bool                     isVirtual() = 0;\n    virtual SP<Aquamarine::IPointer> aq()        = 0;\n\n    struct SMotionEvent {\n        uint32_t     timeMs = 0;\n        Vector2D     delta, unaccel;\n        bool         mouse = false;\n        SP<IPointer> device;\n    };\n\n    struct SMotionAbsoluteEvent {\n        uint32_t timeMs = 0;\n        Vector2D absolute; // 0.0 - 1.0\n        SP<IHID> device;\n    };\n\n    struct SButtonEvent {\n        uint32_t                timeMs = 0;\n        uint32_t                button = 0;\n        wl_pointer_button_state state  = WL_POINTER_BUTTON_STATE_PRESSED;\n        bool                    mouse  = false;\n    };\n\n    struct SAxisEvent {\n        uint32_t                           timeMs            = 0;\n        wl_pointer_axis_source             source            = WL_POINTER_AXIS_SOURCE_WHEEL;\n        wl_pointer_axis                    axis              = WL_POINTER_AXIS_VERTICAL_SCROLL;\n        wl_pointer_axis_relative_direction relativeDirection = WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL;\n        double                             delta             = 0.0;\n        int32_t                            deltaDiscrete     = 0;\n        bool                               mouse             = false;\n    };\n\n    struct SSwipeBeginEvent {\n        uint32_t timeMs  = 0;\n        uint32_t fingers = 0;\n    };\n\n    struct SSwipeUpdateEvent {\n        uint32_t timeMs  = 0;\n        uint32_t fingers = 0;\n        Vector2D delta;\n    };\n\n    struct SSwipeEndEvent {\n        uint32_t timeMs    = 0;\n        bool     cancelled = false;\n    };\n\n    struct SPinchBeginEvent {\n        uint32_t timeMs  = 0;\n        uint32_t fingers = 0;\n    };\n\n    struct SPinchUpdateEvent {\n        uint32_t timeMs  = 0;\n        uint32_t fingers = 0;\n        Vector2D delta;\n        double   scale = 1.0, rotation = 0.0;\n    };\n\n    struct SPinchEndEvent {\n        uint32_t timeMs    = 0;\n        bool     cancelled = false;\n    };\n\n    struct SHoldBeginEvent {\n        uint32_t timeMs  = 0;\n        uint32_t fingers = 0;\n    };\n\n    struct SHoldEndEvent {\n        uint32_t timeMs    = 0;\n        bool     cancelled = false;\n    };\n\n    struct {\n        CSignalT<SMotionEvent>         motion;\n        CSignalT<SMotionAbsoluteEvent> motionAbsolute;\n        CSignalT<SButtonEvent>         button;\n        CSignalT<SAxisEvent>           axis;\n        CSignalT<>                     frame;\n\n        CSignalT<SSwipeBeginEvent>     swipeBegin;\n        CSignalT<SSwipeEndEvent>       swipeEnd;\n        CSignalT<SSwipeUpdateEvent>    swipeUpdate;\n\n        CSignalT<SPinchBeginEvent>     pinchBegin;\n        CSignalT<SPinchEndEvent>       pinchEnd;\n        CSignalT<SPinchUpdateEvent>    pinchUpdate;\n\n        CSignalT<SHoldBeginEvent>      holdBegin;\n        CSignalT<SHoldEndEvent>        holdEnd;\n    } m_pointerEvents;\n\n    bool                 m_connected    = false; // means connected to the cursor\n    std::string          m_boundOutput  = \"\";\n    bool                 m_flipX        = false; // decide to invert horizontal movement\n    bool                 m_flipY        = false; // decide to invert vertical movement\n    bool                 m_isTouchpad   = false;\n    std::optional<float> m_scrollFactor = {};\n\n    WP<IPointer>         m_self;\n};\n"
  },
  {
    "path": "src/devices/ITouch.cpp",
    "content": "#include \"ITouch.hpp\"\n\nuint32_t ITouch::getCapabilities() {\n    return HID_INPUT_CAPABILITY_TOUCH;\n}\n\neHIDType ITouch::getType() {\n    return HID_TYPE_TOUCH;\n}\n"
  },
  {
    "path": "src/devices/ITouch.hpp",
    "content": "#pragma once\n\n#include \"IHID.hpp\"\n#include \"../macros.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\nAQUAMARINE_FORWARD(ITouch);\n\nclass ITouch : public IHID {\n  public:\n    virtual uint32_t               getCapabilities();\n    virtual eHIDType               getType();\n    virtual bool                   isVirtual() = 0;\n    virtual SP<Aquamarine::ITouch> aq()        = 0;\n\n    struct SDownEvent {\n        uint32_t   timeMs  = 0;\n        int32_t    touchID = 0;\n        Vector2D   pos;\n        SP<ITouch> device;\n    };\n\n    struct SUpEvent {\n        uint32_t timeMs  = 0;\n        int32_t  touchID = 0;\n    };\n\n    struct SMotionEvent {\n        uint32_t timeMs  = 0;\n        int32_t  touchID = 0;\n        Vector2D pos;\n    };\n\n    struct SCancelEvent {\n        uint32_t timeMs  = 0;\n        int32_t  touchID = 0;\n    };\n\n    struct {\n        CSignalT<SDownEvent>   down;\n        CSignalT<SUpEvent>     up;\n        CSignalT<SMotionEvent> motion;\n        CSignalT<SCancelEvent> cancel;\n        CSignalT<>             frame;\n    } m_touchEvents;\n\n    std::string m_boundOutput = \"\";\n\n    WP<ITouch>  m_self;\n};\n"
  },
  {
    "path": "src/devices/Keyboard.cpp",
    "content": "#include \"Keyboard.hpp\"\n#include \"../defines.hpp\"\n\n#include <aquamarine/input/Input.hpp>\n\nSP<CKeyboard> CKeyboard::create(SP<Aquamarine::IKeyboard> keeb) {\n    SP<CKeyboard> pKeeb = SP<CKeyboard>(new CKeyboard(keeb));\n\n    pKeeb->m_self = pKeeb;\n\n    return pKeeb;\n}\n\nbool CKeyboard::isVirtual() {\n    return false;\n}\n\nSP<Aquamarine::IKeyboard> CKeyboard::aq() {\n    return m_keyboard.lock();\n}\n\nCKeyboard::CKeyboard(SP<Aquamarine::IKeyboard> keeb) : m_keyboard(keeb) {\n    if (!keeb)\n        return;\n\n    m_listeners.destroy = keeb->events.destroy.listen([this] {\n        m_keyboard.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.key = keeb->events.key.listen([this](const Aquamarine::IKeyboard::SKeyEvent& event) {\n        const auto UPDATED = updatePressed(event.key, event.pressed);\n\n        m_keyboardEvents.key.emit(SKeyEvent{\n            .timeMs  = event.timeMs,\n            .keycode = event.key,\n            .state   = event.pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED,\n        });\n\n        if (UPDATED)\n            updateXkbStateWithKey(event.key + 8, event.pressed);\n    });\n\n    m_listeners.modifiers = keeb->events.modifiers.listen([this] {\n        updateModifiersState();\n\n        m_keyboardEvents.modifiers.emit(SModifiersEvent{\n            .depressed = m_modifiersState.depressed,\n            .latched   = m_modifiersState.latched,\n            .locked    = m_modifiersState.locked,\n            .group     = m_modifiersState.group,\n        });\n    });\n\n    m_deviceName = keeb->getName();\n}\n"
  },
  {
    "path": "src/devices/Keyboard.hpp",
    "content": "#pragma once\n\n#include \"IKeyboard.hpp\"\n\nclass CKeyboard : public IKeyboard {\n  public:\n    static SP<CKeyboard>              create(SP<Aquamarine::IKeyboard> keeb);\n\n    virtual bool                      isVirtual();\n    virtual SP<Aquamarine::IKeyboard> aq();\n\n  private:\n    CKeyboard(SP<Aquamarine::IKeyboard> keeb);\n\n    WP<Aquamarine::IKeyboard> m_keyboard;\n\n    struct {\n        CHyprSignalListener destroy;\n        CHyprSignalListener key;\n        CHyprSignalListener modifiers;\n    } m_listeners;\n};"
  },
  {
    "path": "src/devices/Mouse.cpp",
    "content": "#include \"Mouse.hpp\"\n#include \"../defines.hpp\"\n#include <aquamarine/input/Input.hpp>\n\nSP<CMouse> CMouse::create(SP<Aquamarine::IPointer> mouse) {\n    SP<CMouse> pMouse = SP<CMouse>(new CMouse(mouse));\n\n    pMouse->m_self = pMouse;\n\n    return pMouse;\n}\n\nCMouse::CMouse(SP<Aquamarine::IPointer> mouse_) : m_mouse(mouse_) {\n    if (!m_mouse)\n        return;\n\n    if (auto handle = m_mouse->getLibinputHandle()) {\n        double w = 0, h = 0;\n        m_isTouchpad = libinput_device_has_capability(handle, LIBINPUT_DEVICE_CAP_POINTER) && libinput_device_get_size(handle, &w, &h) == 0;\n    }\n\n    m_listeners.destroy = m_mouse->events.destroy.listen([this] {\n        m_mouse.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.motion = m_mouse->events.move.listen([this](const Aquamarine::IPointer::SMoveEvent& event) {\n        m_pointerEvents.motion.emit(SMotionEvent{\n            .timeMs  = event.timeMs,\n            .delta   = event.delta,\n            .unaccel = event.unaccel,\n            .mouse   = true,\n            .device  = m_self.lock(),\n        });\n    });\n\n    m_listeners.motionAbsolute = m_mouse->events.warp.listen([this](const Aquamarine::IPointer::SWarpEvent& event) {\n        m_pointerEvents.motionAbsolute.emit(SMotionAbsoluteEvent{\n            .timeMs   = event.timeMs,\n            .absolute = event.absolute,\n            .device   = m_self.lock(),\n        });\n    });\n\n    m_listeners.button = m_mouse->events.button.listen([this](const Aquamarine::IPointer::SButtonEvent& event) {\n        m_pointerEvents.button.emit(SButtonEvent{\n            .timeMs = event.timeMs,\n            .button = event.button,\n            .state  = event.pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED,\n            .mouse  = true,\n        });\n    });\n\n    m_listeners.axis = m_mouse->events.axis.listen([this](const Aquamarine::IPointer::SAxisEvent& event) {\n        m_pointerEvents.axis.emit(SAxisEvent{\n            .timeMs            = event.timeMs,\n            .source            = sc<wl_pointer_axis_source>(event.source),\n            .axis              = sc<wl_pointer_axis>(event.axis),\n            .relativeDirection = sc<wl_pointer_axis_relative_direction>(event.direction),\n            .delta             = event.delta,\n            .deltaDiscrete     = event.discrete,\n            .mouse             = true,\n        });\n    });\n\n    m_listeners.frame = m_mouse->events.frame.listen([this] { m_pointerEvents.frame.emit(); });\n\n    m_listeners.swipeBegin = m_mouse->events.swipeBegin.listen([this](const Aquamarine::IPointer::SSwipeBeginEvent& event) {\n        m_pointerEvents.swipeBegin.emit(SSwipeBeginEvent{\n            .timeMs  = event.timeMs,\n            .fingers = event.fingers,\n        });\n    });\n\n    m_listeners.swipeEnd = m_mouse->events.swipeEnd.listen([this](const Aquamarine::IPointer::SSwipeEndEvent& event) {\n        m_pointerEvents.swipeEnd.emit(SSwipeEndEvent{\n            .timeMs    = event.timeMs,\n            .cancelled = event.cancelled,\n        });\n    });\n\n    m_listeners.swipeUpdate = m_mouse->events.swipeUpdate.listen([this](const Aquamarine::IPointer::SSwipeUpdateEvent& event) {\n        m_pointerEvents.swipeUpdate.emit(SSwipeUpdateEvent{\n            .timeMs  = event.timeMs,\n            .fingers = event.fingers,\n            .delta   = event.delta,\n        });\n    });\n\n    m_listeners.pinchBegin = m_mouse->events.pinchBegin.listen([this](const Aquamarine::IPointer::SPinchBeginEvent& event) {\n        m_pointerEvents.pinchBegin.emit(SPinchBeginEvent{\n            .timeMs  = event.timeMs,\n            .fingers = event.fingers,\n        });\n    });\n\n    m_listeners.pinchEnd = m_mouse->events.pinchEnd.listen([this](const Aquamarine::IPointer::SPinchEndEvent& event) {\n        m_pointerEvents.pinchEnd.emit(SPinchEndEvent{\n            .timeMs    = event.timeMs,\n            .cancelled = event.cancelled,\n        });\n    });\n\n    m_listeners.pinchUpdate = m_mouse->events.pinchUpdate.listen([this](const Aquamarine::IPointer::SPinchUpdateEvent& event) {\n        m_pointerEvents.pinchUpdate.emit(SPinchUpdateEvent{\n            .timeMs   = event.timeMs,\n            .fingers  = event.fingers,\n            .delta    = event.delta,\n            .scale    = event.scale,\n            .rotation = event.rotation,\n        });\n    });\n\n    m_listeners.holdBegin = m_mouse->events.holdBegin.listen([this](const Aquamarine::IPointer::SHoldBeginEvent& event) {\n        m_pointerEvents.holdBegin.emit(SHoldBeginEvent{\n            .timeMs  = event.timeMs,\n            .fingers = event.fingers,\n        });\n    });\n\n    m_listeners.holdEnd = m_mouse->events.holdEnd.listen([this](const Aquamarine::IPointer::SHoldEndEvent& event) {\n        m_pointerEvents.holdEnd.emit(SHoldEndEvent{\n            .timeMs    = event.timeMs,\n            .cancelled = event.cancelled,\n        });\n    });\n\n    m_deviceName = m_mouse->getName();\n}\n\nbool CMouse::isVirtual() {\n    return false;\n}\n\nSP<Aquamarine::IPointer> CMouse::aq() {\n    return m_mouse.lock();\n}\n"
  },
  {
    "path": "src/devices/Mouse.hpp",
    "content": "#pragma once\n\n#include \"IPointer.hpp\"\n\nclass CMouse : public IPointer {\n  public:\n    static SP<CMouse>                create(SP<Aquamarine::IPointer> mouse);\n\n    virtual bool                     isVirtual();\n    virtual SP<Aquamarine::IPointer> aq();\n\n  private:\n    CMouse(SP<Aquamarine::IPointer> mouse);\n\n    WP<Aquamarine::IPointer> m_mouse;\n\n    struct {\n        CHyprSignalListener destroy;\n\n        CHyprSignalListener motion;\n        CHyprSignalListener motionAbsolute;\n        CHyprSignalListener button;\n        CHyprSignalListener axis;\n        CHyprSignalListener frame;\n\n        CHyprSignalListener swipeBegin;\n        CHyprSignalListener swipeEnd;\n        CHyprSignalListener swipeUpdate;\n\n        CHyprSignalListener pinchBegin;\n        CHyprSignalListener pinchEnd;\n        CHyprSignalListener pinchUpdate;\n\n        CHyprSignalListener holdBegin;\n        CHyprSignalListener holdEnd;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/devices/Tablet.cpp",
    "content": "#include \"Tablet.hpp\"\n#include \"../defines.hpp\"\n#include \"../protocols/Tablet.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include <aquamarine/input/Input.hpp>\n\nSP<CTablet> CTablet::create(SP<Aquamarine::ITablet> tablet) {\n    SP<CTablet> pTab = SP<CTablet>(new CTablet(tablet));\n\n    pTab->m_self = pTab;\n\n    PROTO::tablet->registerDevice(pTab);\n\n    return pTab;\n}\n\nSP<CTabletTool> CTabletTool::create(SP<Aquamarine::ITabletTool> tablet) {\n    SP<CTabletTool> pTab = SP<CTabletTool>(new CTabletTool(tablet));\n\n    pTab->m_self = pTab;\n\n    PROTO::tablet->registerDevice(pTab);\n\n    return pTab;\n}\n\nSP<CTabletPad> CTabletPad::create(SP<Aquamarine::ITabletPad> tablet) {\n    SP<CTabletPad> pTab = SP<CTabletPad>(new CTabletPad(tablet));\n\n    pTab->m_self = pTab;\n\n    PROTO::tablet->registerDevice(pTab);\n\n    return pTab;\n}\n\nstatic uint32_t aqUpdateToHl(uint32_t aq) {\n    uint32_t result = 0;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_X)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_Y)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_DISTANCE)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_DISTANCE;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_PRESSURE)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_PRESSURE;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_TILT_X)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_TILT_Y)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_ROTATION)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_ROTATION;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_SLIDER)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_SLIDER;\n    if (aq & Aquamarine::AQ_TABLET_TOOL_AXIS_WHEEL)\n        result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_WHEEL;\n    return result;\n}\n\nuint32_t CTablet::getCapabilities() {\n    return HID_INPUT_CAPABILITY_POINTER | HID_INPUT_CAPABILITY_TABLET;\n}\n\nSP<Aquamarine::ITablet> CTablet::aq() {\n    return m_tablet.lock();\n}\n\nCTablet::CTablet(SP<Aquamarine::ITablet> tablet_) : m_tablet(tablet_) {\n    if (!m_tablet)\n        return;\n\n    m_listeners.destroy = m_tablet->events.destroy.listen([this] {\n        m_tablet.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.axis = m_tablet->events.axis.listen([this](const Aquamarine::ITablet::SAxisEvent& event) {\n        m_tabletEvents.axis.emit(SAxisEvent{\n            .tool        = event.tool,\n            .tablet      = m_self.lock(),\n            .timeMs      = event.timeMs,\n            .updatedAxes = aqUpdateToHl(event.updatedAxes),\n            .axis        = event.absolute,\n            .axisDelta   = event.delta,\n            .tilt        = event.tilt,\n            .pressure    = event.pressure,\n            .distance    = event.distance,\n            .rotation    = event.rotation,\n            .slider      = event.slider,\n            .wheelDelta  = event.wheelDelta,\n        });\n    });\n\n    m_listeners.proximity = m_tablet->events.proximity.listen([this](const Aquamarine::ITablet::SProximityEvent& event) {\n        m_tabletEvents.proximity.emit(SProximityEvent{\n            .tool      = event.tool,\n            .tablet    = m_self.lock(),\n            .timeMs    = event.timeMs,\n            .proximity = event.absolute,\n            .in        = event.in,\n        });\n    });\n\n    m_listeners.tip = m_tablet->events.tip.listen([this](const Aquamarine::ITablet::STipEvent& event) {\n        m_tabletEvents.tip.emit(STipEvent{\n            .tool   = event.tool,\n            .tablet = m_self.lock(),\n            .timeMs = event.timeMs,\n            .tip    = event.absolute,\n            .in     = event.down,\n        });\n    });\n\n    m_listeners.button = m_tablet->events.button.listen([this](const Aquamarine::ITablet::SButtonEvent& event) {\n        m_tabletEvents.button.emit(SButtonEvent{\n            .tool   = event.tool,\n            .tablet = m_self.lock(),\n            .timeMs = event.timeMs,\n            .button = event.button,\n            .down   = event.down,\n        });\n    });\n\n    m_deviceName = m_tablet->getName();\n}\n\nCTablet::~CTablet() {\n    PROTO::tablet->recheckRegisteredDevices();\n}\n\neHIDType CTablet::getType() {\n    return HID_TYPE_TABLET;\n}\n\nuint32_t CTabletPad::getCapabilities() {\n    return HID_INPUT_CAPABILITY_TABLET;\n}\n\nSP<Aquamarine::ITabletPad> CTabletPad::aq() {\n    return m_pad.lock();\n}\n\neHIDType CTabletPad::getType() {\n    return HID_TYPE_TABLET_PAD;\n}\n\nCTabletPad::CTabletPad(SP<Aquamarine::ITabletPad> pad_) : m_pad(pad_) {\n    if (!m_pad)\n        return;\n\n    m_listeners.destroy = m_pad->events.destroy.listen([this] {\n        m_pad.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.button = m_pad->events.button.listen([this](const Aquamarine::ITabletPad::SButtonEvent& event) {\n        m_padEvents.button.emit(SButtonEvent{\n            .timeMs = event.timeMs,\n            .button = event.button,\n            .down   = event.down,\n            .mode   = event.mode,\n            .group  = event.group,\n        });\n    });\n\n    m_listeners.ring = m_pad->events.ring.listen([this](const Aquamarine::ITabletPad::SRingEvent& event) {\n        m_padEvents.ring.emit(SRingEvent{\n            .timeMs   = event.timeMs,\n            .finger   = event.source == Aquamarine::ITabletPad::AQ_TABLET_PAD_RING_SOURCE_FINGER,\n            .ring     = event.ring,\n            .position = event.pos,\n            .mode     = event.mode,\n        });\n    });\n\n    m_listeners.strip = m_pad->events.strip.listen([this](const Aquamarine::ITabletPad::SStripEvent& event) {\n        m_padEvents.strip.emit(SStripEvent{\n            .timeMs   = event.timeMs,\n            .finger   = event.source == Aquamarine::ITabletPad::AQ_TABLET_PAD_STRIP_SOURCE_FINGER,\n            .strip    = event.strip,\n            .position = event.pos,\n            .mode     = event.mode,\n        });\n    });\n\n    m_listeners.attach = m_pad->events.attach.listen([] {\n        ; // TODO: this doesn't do anything in aq atm\n    });\n\n    m_deviceName = m_pad->getName();\n}\n\nCTabletPad::~CTabletPad() {\n    PROTO::tablet->recheckRegisteredDevices();\n}\n\nuint32_t CTabletTool::getCapabilities() {\n    return HID_INPUT_CAPABILITY_POINTER | HID_INPUT_CAPABILITY_TABLET;\n}\n\nSP<Aquamarine::ITabletTool> CTabletTool::aq() {\n    return m_tool.lock();\n}\n\neHIDType CTabletTool::getType() {\n    return HID_TYPE_TABLET_TOOL;\n}\n\nCTabletTool::CTabletTool(SP<Aquamarine::ITabletTool> tool_) : m_tool(tool_) {\n    if (!m_tool)\n        return;\n\n    m_listeners.destroyTool = m_tool->events.destroy.listen([this] {\n        m_tool.reset();\n        m_events.destroy.emit();\n    });\n\n    if (m_tool->capabilities & Aquamarine::ITabletTool::AQ_TABLET_TOOL_CAPABILITY_TILT)\n        m_toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_TILT;\n    if (m_tool->capabilities & Aquamarine::ITabletTool::AQ_TABLET_TOOL_CAPABILITY_PRESSURE)\n        m_toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_PRESSURE;\n    if (m_tool->capabilities & Aquamarine::ITabletTool::AQ_TABLET_TOOL_CAPABILITY_DISTANCE)\n        m_toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_DISTANCE;\n    if (m_tool->capabilities & Aquamarine::ITabletTool::AQ_TABLET_TOOL_CAPABILITY_ROTATION)\n        m_toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_ROTATION;\n    if (m_tool->capabilities & Aquamarine::ITabletTool::AQ_TABLET_TOOL_CAPABILITY_SLIDER)\n        m_toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_SLIDER;\n    if (m_tool->capabilities & Aquamarine::ITabletTool::AQ_TABLET_TOOL_CAPABILITY_WHEEL)\n        m_toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_WHEEL;\n\n    m_deviceName = std::format(\"{:x}-{:x}\", m_tool->serial, m_tool->id);\n}\n\nCTabletTool::~CTabletTool() {\n    PROTO::tablet->recheckRegisteredDevices();\n}\n\nSP<CWLSurfaceResource> CTabletTool::getSurface() {\n    return m_surface.lock();\n}\n\nvoid CTabletTool::setSurface(SP<CWLSurfaceResource> surf) {\n    if (surf == m_surface)\n        return;\n\n    if (m_surface) {\n        m_listeners.destroySurface.reset();\n        m_surface.reset();\n    }\n\n    m_surface = surf;\n\n    if (surf) {\n        m_listeners.destroySurface = surf->m_events.destroy.listen([this] {\n            PROTO::tablet->proximityOut(m_self.lock());\n            m_surface.reset();\n            m_listeners.destroySurface.reset();\n        });\n    }\n}\n"
  },
  {
    "path": "src/devices/Tablet.hpp",
    "content": "#pragma once\n\n#include \"IHID.hpp\"\n#include \"../macros.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\nAQUAMARINE_FORWARD(ITablet);\nAQUAMARINE_FORWARD(ITabletTool);\nAQUAMARINE_FORWARD(ITabletPad);\n\nclass CTabletTool;\nclass CTabletPad;\nclass CWLSurfaceResource;\n\n/*\n    A tablet device\n    Tablets don't have an interface for now,\n    if there will be a need it's trivial to do.\n*/\nclass CTablet : public IHID {\n  public:\n    static SP<CTablet> create(SP<Aquamarine::ITablet> tablet);\n    ~CTablet();\n\n    virtual uint32_t        getCapabilities();\n    virtual eHIDType        getType();\n    SP<Aquamarine::ITablet> aq();\n\n    enum eTabletToolAxes : uint16_t {\n        HID_TABLET_TOOL_AXIS_X        = (1 << 0),\n        HID_TABLET_TOOL_AXIS_Y        = (1 << 1),\n        HID_TABLET_TOOL_AXIS_TILT_X   = (1 << 2),\n        HID_TABLET_TOOL_AXIS_TILT_Y   = (1 << 3),\n        HID_TABLET_TOOL_AXIS_DISTANCE = (1 << 4),\n        HID_TABLET_TOOL_AXIS_PRESSURE = (1 << 5),\n        HID_TABLET_TOOL_AXIS_ROTATION = (1 << 6),\n        HID_TABLET_TOOL_AXIS_SLIDER   = (1 << 7),\n        HID_TABLET_TOOL_AXIS_WHEEL    = (1 << 8),\n    };\n\n    struct SAxisEvent {\n        SP<Aquamarine::ITabletTool> tool;\n        SP<CTablet>                 tablet;\n\n        uint32_t                    timeMs      = 0;\n        uint32_t                    updatedAxes = 0; // eTabletToolAxes\n        Vector2D                    axis;\n        Vector2D                    axisDelta;\n        Vector2D                    tilt;\n        double                      pressure   = 0;\n        double                      distance   = 0;\n        double                      rotation   = 0;\n        double                      slider     = 0;\n        double                      wheelDelta = 0;\n    };\n\n    struct SProximityEvent {\n        SP<Aquamarine::ITabletTool> tool;\n        SP<CTablet>                 tablet;\n\n        uint32_t                    timeMs = 0;\n        Vector2D                    proximity;\n        bool                        in = false;\n    };\n\n    struct STipEvent {\n        SP<Aquamarine::ITabletTool> tool;\n        SP<CTablet>                 tablet;\n\n        uint32_t                    timeMs = 0;\n        Vector2D                    tip;\n        bool                        in = false;\n    };\n\n    struct SButtonEvent {\n        SP<Aquamarine::ITabletTool> tool;\n        SP<CTablet>                 tablet;\n\n        uint32_t                    timeMs = 0;\n        uint32_t                    button;\n        bool                        down = false;\n    };\n\n    struct {\n        CSignalT<SAxisEvent>      axis;\n        CSignalT<SProximityEvent> proximity;\n        CSignalT<STipEvent>       tip;\n        CSignalT<SButtonEvent>    button;\n    } m_tabletEvents;\n\n    WP<CTablet> m_self;\n\n    bool        m_relativeInput = false;\n    bool        m_absolutePos   = false;\n    std::string m_boundOutput   = \"\";\n    CBox        m_activeArea;\n    CBox        m_boundBox;\n\n  private:\n    CTablet(SP<Aquamarine::ITablet> tablet);\n\n    WP<Aquamarine::ITablet> m_tablet;\n\n    struct {\n        CHyprSignalListener destroy;\n        CHyprSignalListener axis;\n        CHyprSignalListener proximity;\n        CHyprSignalListener tip;\n        CHyprSignalListener button;\n    } m_listeners;\n};\n\nclass CTabletPad : public IHID {\n  public:\n    static SP<CTabletPad> create(SP<Aquamarine::ITabletPad> pad);\n    ~CTabletPad();\n\n    virtual uint32_t           getCapabilities();\n    virtual eHIDType           getType();\n    SP<Aquamarine::ITabletPad> aq();\n\n    struct SButtonEvent {\n        uint32_t timeMs = 0;\n        uint32_t button = 0;\n        bool     down   = false;\n        uint32_t mode   = 0;\n        uint32_t group  = 0;\n    };\n\n    struct SRingEvent {\n        uint32_t timeMs   = 0;\n        bool     finger   = false;\n        uint32_t ring     = 0;\n        double   position = 0;\n        uint32_t mode     = 0;\n    };\n\n    struct SStripEvent {\n        uint32_t timeMs   = 0;\n        bool     finger   = false;\n        uint32_t strip    = 0;\n        double   position = 0;\n        uint32_t mode     = 0;\n    };\n\n    struct {\n        CSignalT<SButtonEvent>    button;\n        CSignalT<SRingEvent>      ring;\n        CSignalT<SStripEvent>     strip;\n        CSignalT<SP<CTabletTool>> attach;\n    } m_padEvents;\n\n    WP<CTabletPad>  m_self;\n    WP<CTabletTool> m_parent;\n\n  private:\n    CTabletPad(SP<Aquamarine::ITabletPad> pad);\n\n    WP<Aquamarine::ITabletPad> m_pad;\n\n    struct {\n        CHyprSignalListener destroy;\n        CHyprSignalListener ring;\n        CHyprSignalListener strip;\n        CHyprSignalListener button;\n        CHyprSignalListener attach;\n    } m_listeners;\n};\n\nclass CTabletTool : public IHID {\n  public:\n    static SP<CTabletTool> create(SP<Aquamarine::ITabletTool> tool);\n    ~CTabletTool();\n\n    enum eTabletToolType : uint8_t {\n        HID_TABLET_TOOL_TYPE_PEN = 1,\n        HID_TABLET_TOOL_TYPE_ERASER,\n        HID_TABLET_TOOL_TYPE_BRUSH,\n        HID_TABLET_TOOL_TYPE_PENCIL,\n        HID_TABLET_TOOL_TYPE_AIRBRUSH,\n        HID_TABLET_TOOL_TYPE_MOUSE,\n        HID_TABLET_TOOL_TYPE_LENS,\n        HID_TABLET_TOOL_TYPE_TOTEM,\n    };\n\n    enum eTabletToolCapabilities : uint8_t {\n        HID_TABLET_TOOL_CAPABILITY_TILT     = (1 << 0),\n        HID_TABLET_TOOL_CAPABILITY_PRESSURE = (1 << 1),\n        HID_TABLET_TOOL_CAPABILITY_DISTANCE = (1 << 2),\n        HID_TABLET_TOOL_CAPABILITY_ROTATION = (1 << 3),\n        HID_TABLET_TOOL_CAPABILITY_SLIDER   = (1 << 4),\n        HID_TABLET_TOOL_CAPABILITY_WHEEL    = (1 << 5),\n    };\n\n    virtual uint32_t            getCapabilities();\n    SP<Aquamarine::ITabletTool> aq();\n    virtual eHIDType            getType();\n    SP<CWLSurfaceResource>      getSurface();\n    void                        setSurface(SP<CWLSurfaceResource>);\n\n    WP<CTabletTool>             m_self;\n    Vector2D                    m_tilt;\n    bool                        m_active           = false; // true if in proximity\n    uint32_t                    m_toolCapabilities = 0;\n\n    bool                        m_isDown = false;\n    std::vector<uint32_t>       m_buttonsDown;\n    Vector2D                    m_absolutePos; // last known absolute position.\n\n  private:\n    CTabletTool(SP<Aquamarine::ITabletTool> tool);\n\n    WP<CWLSurfaceResource>      m_surface;\n    WP<Aquamarine::ITabletTool> m_tool;\n\n    struct {\n        CHyprSignalListener destroySurface;\n        CHyprSignalListener destroyTool;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/devices/TouchDevice.cpp",
    "content": "#include \"TouchDevice.hpp\"\n#include \"../defines.hpp\"\n#include <aquamarine/input/Input.hpp>\n\nSP<CTouchDevice> CTouchDevice::create(SP<Aquamarine::ITouch> touch) {\n    SP<CTouchDevice> pTouch = SP<CTouchDevice>(new CTouchDevice(touch));\n\n    pTouch->m_self = pTouch;\n\n    return pTouch;\n}\n\nCTouchDevice::CTouchDevice(SP<Aquamarine::ITouch> touch_) : m_touch(touch_) {\n    if (!m_touch)\n        return;\n\n    m_listeners.destroy = m_touch->events.destroy.listen([this] {\n        m_events.destroy.emit();\n        m_touch.reset();\n    });\n\n    m_listeners.down = m_touch->events.down.listen([this](const Aquamarine::ITouch::SDownEvent& event) {\n        m_touchEvents.down.emit(SDownEvent{\n            .timeMs  = event.timeMs,\n            .touchID = event.touchID,\n            .pos     = event.pos,\n            .device  = m_self.lock(),\n        });\n    });\n\n    m_listeners.up = m_touch->events.up.listen([this](const Aquamarine::ITouch::SUpEvent& event) {\n        m_touchEvents.up.emit(SUpEvent{\n            .timeMs  = event.timeMs,\n            .touchID = event.touchID,\n        });\n    });\n\n    m_listeners.motion = m_touch->events.move.listen([this](const Aquamarine::ITouch::SMotionEvent& event) {\n        m_touchEvents.motion.emit(SMotionEvent{\n            .timeMs  = event.timeMs,\n            .touchID = event.touchID,\n            .pos     = event.pos,\n        });\n    });\n\n    m_listeners.cancel = m_touch->events.cancel.listen([this](const Aquamarine::ITouch::SCancelEvent& event) {\n        m_touchEvents.cancel.emit(SCancelEvent{\n            .timeMs  = event.timeMs,\n            .touchID = event.touchID,\n        });\n    });\n\n    m_listeners.frame = m_touch->events.frame.listen([this] { m_touchEvents.frame.emit(); });\n\n    m_deviceName = m_touch->getName();\n}\n\nbool CTouchDevice::isVirtual() {\n    return false;\n}\n\nSP<Aquamarine::ITouch> CTouchDevice::aq() {\n    return m_touch.lock();\n}\n"
  },
  {
    "path": "src/devices/TouchDevice.hpp",
    "content": "#pragma once\n\n#include \"ITouch.hpp\"\n\nclass CTouchDevice : public ITouch {\n  public:\n    static SP<CTouchDevice>        create(SP<Aquamarine::ITouch> touch);\n\n    virtual bool                   isVirtual();\n    virtual SP<Aquamarine::ITouch> aq();\n\n  private:\n    CTouchDevice(SP<Aquamarine::ITouch> touch);\n\n    WP<Aquamarine::ITouch> m_touch;\n\n    struct {\n        CHyprSignalListener destroy;\n        CHyprSignalListener down;\n        CHyprSignalListener up;\n        CHyprSignalListener motion;\n        CHyprSignalListener cancel;\n        CHyprSignalListener frame;\n    } m_listeners;\n};"
  },
  {
    "path": "src/devices/VirtualKeyboard.cpp",
    "content": "#include \"VirtualKeyboard.hpp\"\n#include \"../defines.hpp\"\n#include \"../protocols/VirtualKeyboard.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include <wayland-server-protocol.h>\n\nSP<CVirtualKeyboard> CVirtualKeyboard::create(SP<CVirtualKeyboardV1Resource> keeb) {\n    SP<CVirtualKeyboard> pKeeb = SP<CVirtualKeyboard>(new CVirtualKeyboard(keeb));\n\n    pKeeb->m_self = pKeeb;\n\n    return pKeeb;\n}\n\nCVirtualKeyboard::CVirtualKeyboard(SP<CVirtualKeyboardV1Resource> keeb_) : m_keyboard(keeb_) {\n    if (!keeb_)\n        return;\n\n    m_listeners.destroy = keeb_->m_events.destroy.listen([this] {\n        m_keyboard.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.key       = keeb_->m_events.key.listen([this](const auto& event) {\n        updatePressed(event.keycode, event.state == WL_KEYBOARD_KEY_STATE_PRESSED);\n        m_keyboardEvents.key.emit(event);\n    });\n    m_listeners.modifiers = keeb_->m_events.modifiers.listen([this](const SModifiersEvent& event) {\n        updateModifiers(event.depressed, event.latched, event.locked, event.group);\n        m_keyboardEvents.modifiers.emit(SModifiersEvent{\n            .depressed = m_modifiersState.depressed,\n            .latched   = m_modifiersState.latched,\n            .locked    = m_modifiersState.locked,\n            .group     = m_modifiersState.group,\n        });\n    });\n    m_listeners.keymap    = keeb_->m_events.keymap.listen([this](const SKeymapEvent& event) {\n        if (m_xkbKeymap)\n            xkb_keymap_unref(m_xkbKeymap);\n        m_xkbKeymap        = xkb_keymap_ref(event.keymap);\n        m_keymapOverridden = true;\n        updateXKBTranslationState(m_xkbKeymap);\n        updateKeymapFD();\n        m_keyboardEvents.keymap.emit(event);\n    });\n\n    m_deviceName = keeb_->m_name;\n\n    const auto SHARESTATES = g_pConfigManager->getDeviceInt(m_deviceName, \"share_states\", \"input:virtualkeyboard:share_states\");\n    m_shareStates          = SHARESTATES != 0;\n    m_shareStatesAuto      = SHARESTATES == 2;\n}\n\nbool CVirtualKeyboard::isVirtual() {\n    return true;\n}\n\nSP<Aquamarine::IKeyboard> CVirtualKeyboard::aq() {\n    return nullptr;\n}\n\nwl_client* CVirtualKeyboard::getClient() {\n    if (m_keyboard.expired())\n        return nullptr;\n    return m_keyboard->client();\n}\n"
  },
  {
    "path": "src/devices/VirtualKeyboard.hpp",
    "content": "#pragma once\n\n#include \"IKeyboard.hpp\"\n\nclass CVirtualKeyboardV1Resource;\n\nclass CVirtualKeyboard : public IKeyboard {\n  public:\n    static SP<CVirtualKeyboard>       create(SP<CVirtualKeyboardV1Resource> keeb);\n\n    virtual bool                      isVirtual();\n    virtual SP<Aquamarine::IKeyboard> aq();\n\n    virtual wl_client*                getClient();\n\n  private:\n    CVirtualKeyboard(SP<CVirtualKeyboardV1Resource> keeb);\n\n    WP<CVirtualKeyboardV1Resource> m_keyboard;\n\n    struct {\n        CHyprSignalListener destroy;\n        CHyprSignalListener key;\n        CHyprSignalListener modifiers;\n        CHyprSignalListener keymap;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/devices/VirtualPointer.cpp",
    "content": "#include \"VirtualPointer.hpp\"\n#include \"../protocols/VirtualPointer.hpp\"\n#include <aquamarine/input/Input.hpp>\n\nSP<CVirtualPointer> CVirtualPointer::create(SP<CVirtualPointerV1Resource> resource) {\n    SP<CVirtualPointer> pPointer = SP<CVirtualPointer>(new CVirtualPointer(resource));\n\n    pPointer->m_self = pPointer;\n\n    return pPointer;\n}\n\nCVirtualPointer::CVirtualPointer(SP<CVirtualPointerV1Resource> resource) : m_pointer(resource) {\n    if UNLIKELY (!resource->good())\n        return;\n\n    m_listeners.destroy = m_pointer->m_events.destroy.listen([this] {\n        m_pointer.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.motion         = m_pointer->m_events.move.listen([this](SMotionEvent event) {\n        event.device = m_self.lock();\n        m_pointerEvents.motion.emit(event);\n    });\n    m_listeners.motionAbsolute = m_pointer->m_events.warp.listen([this](SMotionAbsoluteEvent event) {\n        // we need to unpack the event and add our device here because it's required to calculate the position correctly\n        event.device = m_self.lock();\n        m_pointerEvents.motionAbsolute.emit(event);\n    });\n    m_listeners.button         = m_pointer->m_events.button.forward(m_pointerEvents.button);\n    m_listeners.axis           = m_pointer->m_events.axis.forward(m_pointerEvents.axis);\n    m_listeners.frame          = m_pointer->m_events.frame.forward(m_pointerEvents.frame);\n    m_listeners.swipeBegin     = m_pointer->m_events.swipeBegin.forward(m_pointerEvents.swipeBegin);\n    m_listeners.swipeEnd       = m_pointer->m_events.swipeEnd.forward(m_pointerEvents.swipeEnd);\n    m_listeners.swipeUpdate    = m_pointer->m_events.swipeUpdate.forward(m_pointerEvents.swipeUpdate);\n    m_listeners.pinchBegin     = m_pointer->m_events.pinchBegin.forward(m_pointerEvents.pinchBegin);\n    m_listeners.pinchEnd       = m_pointer->m_events.pinchEnd.forward(m_pointerEvents.pinchEnd);\n    m_listeners.pinchUpdate    = m_pointer->m_events.pinchUpdate.forward(m_pointerEvents.pinchUpdate);\n    m_listeners.holdBegin      = m_pointer->m_events.holdBegin.forward(m_pointerEvents.holdBegin);\n    m_listeners.holdEnd        = m_pointer->m_events.holdEnd.forward(m_pointerEvents.holdEnd);\n\n    m_boundOutput = resource->m_boundOutput ? resource->m_boundOutput->m_name : \"\";\n\n    m_deviceName = m_pointer->m_name;\n}\n\nbool CVirtualPointer::isVirtual() {\n    return true;\n}\n\nSP<Aquamarine::IPointer> CVirtualPointer::aq() {\n    return nullptr;\n}\n"
  },
  {
    "path": "src/devices/VirtualPointer.hpp",
    "content": "#pragma once\n\n#include \"IPointer.hpp\"\n\nclass CVirtualPointerV1Resource;\n\nclass CVirtualPointer : public IPointer {\n  public:\n    static SP<CVirtualPointer>       create(SP<CVirtualPointerV1Resource> resource);\n\n    virtual bool                     isVirtual();\n    virtual SP<Aquamarine::IPointer> aq();\n\n  private:\n    CVirtualPointer(SP<CVirtualPointerV1Resource>);\n\n    WP<CVirtualPointerV1Resource> m_pointer;\n\n    struct {\n        CHyprSignalListener destroy;\n\n        CHyprSignalListener motion;\n        CHyprSignalListener motionAbsolute;\n        CHyprSignalListener button;\n        CHyprSignalListener axis;\n        CHyprSignalListener frame;\n\n        CHyprSignalListener swipeBegin;\n        CHyprSignalListener swipeEnd;\n        CHyprSignalListener swipeUpdate;\n\n        CHyprSignalListener pinchBegin;\n        CHyprSignalListener pinchEnd;\n        CHyprSignalListener pinchUpdate;\n\n        CHyprSignalListener holdBegin;\n        CHyprSignalListener holdEnd;\n    } m_listeners;\n};"
  },
  {
    "path": "src/event/EventBus.cpp",
    "content": "#include \"EventBus.hpp\"\n\nusing namespace Event;\n\nUP<CEventBus>& Event::bus() {\n    static UP<CEventBus> p = makeUnique<CEventBus>();\n    return p;\n}\n"
  },
  {
    "path": "src/event/EventBus.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\n#include \"../devices/IPointer.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../devices/Tablet.hpp\"\n#include \"../devices/ITouch.hpp\"\n\n#include \"../desktop/DesktopTypes.hpp\"\n\n#include \"../SharedDefs.hpp\"\n\nnamespace Desktop {\n    enum eFocusReason : uint8_t;\n}\nnamespace Event {\n    struct SCallbackInfo {\n        bool cancelled = false; /* on cancellable events, will cancel the event. */\n    };\n\n    class CEventBus {\n      public:\n        CEventBus()  = default;\n        ~CEventBus() = default;\n\n        template <typename... Args>\n        using Event = CSignalT<Args...>;\n\n        template <typename... Args>\n        using Cancellable = CSignalT<Args..., SCallbackInfo&>;\n\n        struct {\n            Event<> ready;\n            Event<> tick;\n\n            struct {\n                Event<PHLWINDOW>                        open;\n                Event<PHLWINDOW>                        openEarly;\n                Event<PHLWINDOW>                        destroy;\n                Event<PHLWINDOW>                        close;\n                Event<PHLWINDOW>                        kill;\n                Event<PHLWINDOW, Desktop::eFocusReason> active;\n                Event<PHLWINDOW>                        urgent;\n                Event<PHLWINDOW>                        title;\n                Event<PHLWINDOW>                        class_;\n                Event<PHLWINDOW>                        pin;\n                Event<PHLWINDOW>                        fullscreen;\n                Event<PHLWINDOW>                        updateRules;\n                Event<PHLWINDOW, PHLWORKSPACE>          moveToWorkspace;\n            } window;\n\n            struct {\n                Event<PHLLS> opened;\n                Event<PHLLS> closed;\n                Event<PHLLS> updateRules;\n            } layer;\n\n            struct {\n                struct {\n                    Cancellable<Vector2D>               move;\n                    Cancellable<IPointer::SButtonEvent> button;\n                    Cancellable<IPointer::SAxisEvent>   axis;\n                } mouse;\n\n                struct {\n                    Cancellable<IKeyboard::SKeyEvent>        key;\n                    Event<SP<IKeyboard>, const std::string&> layout;\n                    Event<SP<CWLSurfaceResource>>            focus;\n                } keyboard;\n\n                struct {\n                    Cancellable<CTablet::SAxisEvent>      axis;\n                    Cancellable<CTablet::SButtonEvent>    button;\n                    Cancellable<CTablet::SProximityEvent> proximity;\n                    Cancellable<CTablet::STipEvent>       tip;\n                } tablet;\n\n                struct {\n                    Cancellable<ITouch::SCancelEvent> cancel;\n                    Cancellable<ITouch::SDownEvent>   down;\n                    Cancellable<ITouch::SUpEvent>     up;\n                    Cancellable<ITouch::SMotionEvent> motion;\n                } touch;\n            } input;\n\n            struct {\n                Event<PHLMONITOR>   pre;\n                Event<eRenderStage> stage;\n            } render;\n\n            struct {\n                Event<bool /* state start/stop */, uint8_t /* eScreenshareType */, const std::string& /* name */> state;\n            } screenshare;\n\n            struct {\n                struct {\n                    Cancellable<IPointer::SSwipeBeginEvent>  begin;\n                    Cancellable<IPointer::SSwipeEndEvent>    end;\n                    Cancellable<IPointer::SSwipeUpdateEvent> update;\n                } swipe;\n\n                struct {\n                    Cancellable<IPointer::SPinchBeginEvent>  begin;\n                    Cancellable<IPointer::SPinchEndEvent>    end;\n                    Cancellable<IPointer::SPinchUpdateEvent> update;\n                } pinch;\n            } gesture;\n\n            struct {\n                Event<PHLMONITOR> newMon;\n                Event<PHLMONITOR> preAdded;\n                Event<PHLMONITOR> added;\n                Event<PHLMONITOR> preRemoved;\n                Event<PHLMONITOR> removed;\n                Event<PHLMONITOR> preCommit;\n                Event<PHLMONITOR> focused;\n\n                Event<>           layoutChanged;\n            } monitor;\n\n            struct {\n                Event<PHLWORKSPACE, PHLMONITOR> moveToMonitor;\n                Event<PHLWORKSPACE>             active;\n                Event<PHLWORKSPACEREF>          created;\n                Event<PHLWORKSPACEREF>          removed;\n            } workspace;\n\n            struct {\n                Event<> preReload;\n                Event<> reloaded;\n            } config;\n\n            struct {\n                Event<const std::string&> submap;\n            } keybinds;\n\n        } m_events;\n    };\n\n    UP<CEventBus>& bus();\n};\n"
  },
  {
    "path": "src/helpers/AnimatedVariable.hpp",
    "content": "#pragma once\n\n#include <hyprutils/animation/AnimatedVariable.hpp>\n\n#include \"Color.hpp\"\n#include \"../defines.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n\nenum eAVarDamagePolicy : int8_t {\n    AVARDAMAGE_NONE   = -1,\n    AVARDAMAGE_ENTIRE = 0,\n    AVARDAMAGE_BORDER,\n    AVARDAMAGE_SHADOW\n};\n\nenum eAnimatedVarType : int8_t {\n    AVARTYPE_INVALID = -1,\n    AVARTYPE_FLOAT,\n    AVARTYPE_VECTOR,\n    AVARTYPE_COLOR\n};\n\n// Utility to bind a type with its corresponding eAnimatedVarType\ntemplate <class T>\n// NOLINTNEXTLINE(readability-identifier-naming)\nstruct STypeToAnimatedVarType_t {\n    static constexpr eAnimatedVarType value = AVARTYPE_INVALID;\n};\n\ntemplate <>\nstruct STypeToAnimatedVarType_t<float> {\n    static constexpr eAnimatedVarType value = AVARTYPE_FLOAT;\n};\n\ntemplate <>\nstruct STypeToAnimatedVarType_t<Vector2D> {\n    static constexpr eAnimatedVarType value = AVARTYPE_VECTOR;\n};\n\ntemplate <>\nstruct STypeToAnimatedVarType_t<CHyprColor> {\n    static constexpr eAnimatedVarType value = AVARTYPE_COLOR;\n};\n\ntemplate <class T>\ninline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t<T>::value;\n\n// Utility to define a concept as a list of possible type\ntemplate <class T, class... U>\nconcept OneOf = (... or std::same_as<T, U>);\n\n// Concept to describe which type can be placed into CAnimatedVariable\n// This is mainly to get better errors if we put a type that's not supported\n// Otherwise template errors are ugly\ntemplate <class T>\nconcept Animable = OneOf<T, Vector2D, float, CHyprColor>;\n\nstruct SAnimationContext {\n    PHLWINDOWREF      pWindow;\n    PHLWORKSPACEREF   pWorkspace;\n    PHLLSREF          pLayer;\n\n    eAVarDamagePolicy eDamagePolicy = AVARDAMAGE_NONE;\n};\n\ntemplate <Animable VarType>\nusing CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable<VarType, SAnimationContext>;\n\ntemplate <Animable VarType>\nusing PHLANIMVAR = UP<CAnimatedVariable<VarType>>;\n\ntemplate <Animable VarType>\nusing PHLANIMVARREF = WP<CAnimatedVariable<VarType>>;\n"
  },
  {
    "path": "src/helpers/AsyncDialogBox.cpp",
    "content": "#include \"AsyncDialogBox.hpp\"\n#include \"./fs/FsUtils.hpp\"\n#include <csignal>\n#include <algorithm>\n#include <unistd.h>\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../desktop/rule/windowRule/WindowRule.hpp\"\n#include \"../desktop/rule/Engine.hpp\"\n\nusing namespace Hyprutils::OS;\n\nstatic std::vector<std::pair<pid_t, WP<CAsyncDialogBox>>> asyncDialogBoxes;\n\n//\nSP<CAsyncDialogBox> CAsyncDialogBox::create(const std::string& title, const std::string& description, std::vector<std::string> buttons) {\n    if (!NFsUtils::executableExistsInPath(\"hyprland-dialog\")) {\n        Log::logger->log(Log::ERR, \"CAsyncDialogBox: cannot create, no hyprland-dialog\");\n        return nullptr;\n    }\n\n    auto dialog = SP<CAsyncDialogBox>(new CAsyncDialogBox(title, description, buttons));\n\n    dialog->m_selfWeakReference = dialog;\n\n    return dialog;\n}\n\nbool CAsyncDialogBox::isAsyncDialogBox(pid_t pid) {\n    return std::ranges::find_if(asyncDialogBoxes, [pid](const auto& e) { return e.first == pid; }) != asyncDialogBoxes.end();\n}\n\nbool CAsyncDialogBox::isPriorityDialogBox(pid_t pid) {\n    for (const auto& [p, db] : asyncDialogBoxes) {\n        if (p != pid)\n            continue;\n\n        return db && db->m_priority;\n    }\n\n    return false;\n}\n\nCAsyncDialogBox::CAsyncDialogBox(const std::string& title, const std::string& description, std::vector<std::string> buttons) :\n    m_title(title), m_description(description), m_buttons(buttons) {\n    ;\n}\n\nstatic int onFdWrite(int fd, uint32_t mask, void* data) {\n    auto box = sc<CAsyncDialogBox*>(data);\n\n    // lock the box to prevent a UAF\n    auto lock = box->lockSelf();\n\n    box->onWrite(fd, mask);\n\n    return 0;\n}\n\nvoid CAsyncDialogBox::onWrite(int fd, uint32_t mask) {\n    if (mask & WL_EVENT_READABLE) {\n        std::array<char, 1024> buf;\n        int                    ret = 0;\n\n        // make the FD nonblock for a moment\n        // TODO: can we avoid this without risking a blocking read()?\n        int fdFlags = fcntl(fd, F_GETFL, 0);\n        if (fcntl(fd, F_SETFL, fdFlags | O_NONBLOCK) < 0) {\n            Log::logger->log(Log::ERR, \"CAsyncDialogBox::onWrite: fcntl 1 failed!\");\n            return;\n        }\n\n        while ((ret = read(m_pipeReadFd.get(), buf.data(), 1023)) > 0) {\n            m_stdout += std::string_view{(buf.data()), sc<size_t>(ret)};\n        }\n\n        // restore the flags (otherwise libwayland won't give us a hangup)\n        if (fcntl(fd, F_SETFL, fdFlags) < 0) {\n            Log::logger->log(Log::ERR, \"CAsyncDialogBox::onWrite: fcntl 2 failed!\");\n            return;\n        }\n    }\n\n    if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {\n        Log::logger->log(Log::DEBUG, \"CAsyncDialogBox: dialog {:x} hung up, closed.\");\n\n        m_promiseResolver->resolve(m_stdout);\n        std::erase_if(asyncDialogBoxes, [this](const auto& e) { return e.first == m_dialogPid; });\n\n        wl_event_source_remove(m_readEventSource);\n        m_selfReference.reset();\n        return;\n    }\n}\n\nSP<CPromise<std::string>> CAsyncDialogBox::open() {\n    std::string buttonsString = \"\";\n    for (auto& b : m_buttons) {\n        buttonsString += b + \";\";\n    }\n    if (!buttonsString.empty())\n        buttonsString.pop_back();\n\n    CProcess proc(\"hyprland-dialog\", std::vector<std::string>{\"--title\", m_title, \"--text\", m_description, \"--buttons\", buttonsString});\n\n    int      outPipe[2];\n    if (pipe(outPipe)) {\n        Log::logger->log(Log::ERR, \"CAsyncDialogBox::open: failed to pipe()\");\n        return nullptr;\n    }\n\n    m_pipeReadFd = CFileDescriptor(outPipe[0]);\n\n    proc.setStdoutFD(outPipe[1]);\n\n    m_readEventSource = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, m_pipeReadFd.get(), WL_EVENT_READABLE, ::onFdWrite, this);\n\n    if (!m_readEventSource) {\n        Log::logger->log(Log::ERR, \"CAsyncDialogBox::open: failed to add read fd to loop\");\n        return nullptr;\n    }\n\n    m_selfReference = m_selfWeakReference.lock();\n\n    if (!m_execRuleToken.empty())\n        proc.addEnv(Desktop::Rule::EXEC_RULE_ENV_NAME, m_execRuleToken);\n\n    if (!proc.runAsync()) {\n        Log::logger->log(Log::ERR, \"CAsyncDialogBox::open: failed to run async\");\n        wl_event_source_remove(m_readEventSource);\n        return nullptr;\n    }\n\n    m_dialogPid = proc.pid();\n    asyncDialogBoxes.emplace_back(std::make_pair<>(m_dialogPid, m_selfWeakReference));\n\n    // close the write fd, only the dialog owns it now\n    close(outPipe[1]);\n\n    auto promise = CPromise<std::string>::make([this](SP<CPromiseResolver<std::string>> r) { m_promiseResolver = r; });\n\n    return promise;\n}\n\nvoid CAsyncDialogBox::kill() {\n    if (m_dialogPid <= 0)\n        return;\n\n    ::kill(m_dialogPid, SIGKILL);\n}\n\nbool CAsyncDialogBox::isRunning() const {\n    return m_readEventSource;\n}\n\npid_t CAsyncDialogBox::getPID() const {\n    return m_dialogPid;\n}\n\nSP<CAsyncDialogBox> CAsyncDialogBox::lockSelf() {\n    return m_selfWeakReference.lock();\n}\n\nvoid CAsyncDialogBox::setExecRule(std::string&& s) {\n    auto rule       = Desktop::Rule::CWindowRule::buildFromExecString(std::move(s));\n    m_execRuleToken = rule->execToken();\n    Desktop::Rule::ruleEngine()->registerRule(std::move(rule));\n}\n"
  },
  {
    "path": "src/helpers/AsyncDialogBox.hpp",
    "content": "#pragma once\n\n#include \"../macros.hpp\"\n#include \"./memory/Memory.hpp\"\n#include \"./defer/Promise.hpp\"\n\n#include <vector>\n#include <functional>\n\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n\nstruct wl_event_source;\n\nclass CAsyncDialogBox {\n  public:\n    static SP<CAsyncDialogBox> create(const std::string& title, const std::string& description, std::vector<std::string> buttons);\n    static bool                isAsyncDialogBox(pid_t pid);\n    static bool                isPriorityDialogBox(pid_t pid);\n\n    CAsyncDialogBox(const CAsyncDialogBox&)                     = delete;\n    CAsyncDialogBox(CAsyncDialogBox&&)                          = delete;\n    CAsyncDialogBox&          operator=(const CAsyncDialogBox&) = delete;\n    CAsyncDialogBox&          operator=(CAsyncDialogBox&&)      = delete;\n\n    SP<CPromise<std::string>> open();\n    void                      kill();\n    bool                      isRunning() const;\n    pid_t                     getPID() const;\n    void                      setExecRule(std::string&& s);\n\n    SP<CAsyncDialogBox>       lockSelf();\n\n    // focus priority, only permission popups\n    bool m_priority = false;\n\n    void onWrite(int fd, uint32_t mask);\n\n  private:\n    CAsyncDialogBox(const std::string& title, const std::string& description, std::vector<std::string> buttons);\n\n    pid_t                             m_dialogPid       = 0;\n    wl_event_source*                  m_readEventSource = nullptr;\n    Hyprutils::OS::CFileDescriptor    m_pipeReadFd;\n    std::string                       m_stdout        = \"\";\n    std::string                       m_execRuleToken = \"\";\n\n    const std::string                 m_title;\n    const std::string                 m_description;\n    const std::vector<std::string>    m_buttons;\n\n    SP<CPromiseResolver<std::string>> m_promiseResolver;\n\n    // WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers\n    SP<CAsyncDialogBox> m_selfReference;\n    WP<CAsyncDialogBox> m_selfWeakReference;\n};\n"
  },
  {
    "path": "src/helpers/ByteOperations.hpp",
    "content": "#pragma once\n\n#include <type_traits>\n\n#define ULL unsigned long long\n#define LD  long double\n\nconstexpr ULL operator\"\"_kB(const ULL BYTES) {\n    return BYTES * 1024;\n}\nconstexpr ULL operator\"\"_MB(const ULL BYTES) {\n    return BYTES * 1024 * 1024;\n}\nconstexpr ULL operator\"\"_GB(const ULL BYTES) {\n    return BYTES * 1024 * 1024 * 1024;\n}\nconstexpr ULL operator\"\"_TB(const ULL BYTES) {\n    return BYTES * 1024 * 1024 * 1024 * 1024;\n}\nconstexpr LD operator\"\"_kB(const LD BYTES) {\n    return BYTES * 1024;\n}\nconstexpr LD operator\"\"_MB(const LD BYTES) {\n    return BYTES * 1024 * 1024;\n}\nconstexpr LD operator\"\"_GB(const LD BYTES) {\n    return BYTES * 1024 * 1024 * 1024;\n}\nconstexpr LD operator\"\"_TB(const LD BYTES) {\n    return BYTES * 1024 * 1024 * 1024 * 1024;\n}\n\n//NOLINTBEGIN\ntemplate <typename T>\nusing internal_hl_acceptable_byte_operation_type = typename std::enable_if<std::is_trivially_constructible<T, ULL>::value || std::is_trivially_constructible<T, LD>::value>::type;\n\ntemplate <typename X, typename = internal_hl_acceptable_byte_operation_type<X>>\nconstexpr X kBtoBytes(const X kB) {\n    return kB * 1024;\n}\ntemplate <typename X, typename = internal_hl_acceptable_byte_operation_type<X>>\nconstexpr X MBtoBytes(const X MB) {\n    return MB * 1024 * 1024;\n}\ntemplate <typename X, typename = internal_hl_acceptable_byte_operation_type<X>>\nconstexpr X GBtoBytes(const X GB) {\n    return GB * 1024 * 1024 * 1024;\n}\ntemplate <typename X, typename = internal_hl_acceptable_byte_operation_type<X>>\nconstexpr X TBtoBytes(const X TB) {\n    return TB * 1024 * 1024 * 1024 * 1024;\n}\n//NOLINTEND\n\n#undef ULL\n#undef LD"
  },
  {
    "path": "src/helpers/CMType.cpp",
    "content": "#include \"CMType.hpp\"\n#include <optional>\n#include <string>\n#include <unordered_map>\n\nstatic std::unordered_map<std::string, NCMType::eCMType> const table = {{\"auto\", NCMType::CM_AUTO},   {\"srgb\", NCMType::CM_SRGB}, {\"wide\", NCMType::CM_WIDE},\n                                                                        {\"edid\", NCMType::CM_EDID},   {\"hdr\", NCMType::CM_HDR},   {\"hdredid\", NCMType::CM_HDR_EDID},\n                                                                        {\"dcip3\", NCMType::CM_DCIP3}, {\"dp3\", NCMType::CM_DP3},   {\"adobe\", NCMType::CM_ADOBE}};\n\nstd::optional<NCMType::eCMType>                                NCMType::fromString(const std::string cmType) {\n    auto it = table.find(cmType);\n    if (it == table.end())\n        return std::nullopt;\n    return it->second;\n}\n\nstd::string NCMType::toString(eCMType cmType) {\n    for (const auto& [key, value] : table) {\n        if (value == cmType)\n            return key;\n    }\n    return \"\";\n}\n"
  },
  {
    "path": "src/helpers/CMType.hpp",
    "content": "#include <cstdint>\n#include <string>\n#include <optional>\n\nnamespace NCMType {\n    enum eCMType : uint8_t {\n        CM_AUTO = 0, // subject to change. srgb for 8bpc, wide for 10bpc if supported\n        CM_SRGB,     // default, sRGB primaries\n        CM_WIDE,     // wide color gamut, BT2020 primaries\n        CM_EDID,     // primaries from edid (known to be inaccurate)\n        CM_HDR,      // wide color gamut and HDR PQ transfer function\n        CM_HDR_EDID, // same as CM_HDR with edid primaries\n        CM_DCIP3,    // movie theatre with greenish white point\n        CM_DP3,      // applle P3 variant with blueish white point\n        CM_ADOBE,    // adobe colorspace\n    };\n\n    std::optional<eCMType> fromString(const std::string cmType);\n    std::string            toString(eCMType cmType);\n}\n"
  },
  {
    "path": "src/helpers/Color.cpp",
    "content": "#include \"Color.hpp\"\n\n#define ALPHA(c) ((double)(((c) >> 24) & 0xff) / 255.0)\n#define RED(c)   ((double)(((c) >> 16) & 0xff) / 255.0)\n#define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0)\n#define BLUE(c)  ((double)(((c)) & 0xff) / 255.0)\n\nCHyprColor::CHyprColor() = default;\n\nCHyprColor::CHyprColor(float r_, float g_, float b_, float a_) : r(r_), g(g_), b(b_), a(a_) {\n    m_okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{r, g, b}).asOkLab();\n}\n\nCHyprColor::CHyprColor(uint64_t hex) : r(RED(hex)), g(GREEN(hex)), b(BLUE(hex)), a(ALPHA(hex)) {\n    m_okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{r, g, b}).asOkLab();\n}\n\nCHyprColor::CHyprColor(const Hyprgraphics::CColor& color, float a_) : a(a_) {\n    const auto SRGB = color.asRgb();\n    r               = SRGB.r;\n    g               = SRGB.g;\n    b               = SRGB.b;\n\n    m_okLab = color.asOkLab();\n}\n\nuint32_t CHyprColor::getAsHex() const {\n    return sc<uint32_t>(a * 255.f) * 0x1000000 + sc<uint32_t>(r * 255.f) * 0x10000 + sc<uint32_t>(g * 255.f) * 0x100 + sc<uint32_t>(b * 255.f) * 0x1;\n}\n\nHyprgraphics::CColor::SSRGB CHyprColor::asRGB() const {\n    return {r, g, b};\n}\n\nHyprgraphics::CColor::SOkLab CHyprColor::asOkLab() const {\n    return m_okLab;\n}\n\nHyprgraphics::CColor::SHSL CHyprColor::asHSL() const {\n    return Hyprgraphics::CColor(m_okLab).asHSL();\n}\n\nCHyprColor CHyprColor::stripA() const {\n    return {r, g, b, 1.F};\n}\n\nCHyprColor CHyprColor::modifyA(float newa) const {\n    return {r, g, b, newa};\n}\n"
  },
  {
    "path": "src/helpers/Color.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <hyprgraphics/color/Color.hpp>\n#include \"../macros.hpp\"\n\nclass CHyprColor {\n  public:\n    CHyprColor();\n    CHyprColor(float r, float g, float b, float a);\n    CHyprColor(const Hyprgraphics::CColor& col, float a);\n    CHyprColor(uint64_t);\n\n    // AR32\n    uint32_t                     getAsHex() const;\n    Hyprgraphics::CColor::SSRGB  asRGB() const;\n    Hyprgraphics::CColor::SOkLab asOkLab() const;\n    Hyprgraphics::CColor::SHSL   asHSL() const;\n    CHyprColor                   stripA() const;\n    CHyprColor                   modifyA(float newa) const;\n\n    //\n    bool operator==(const CHyprColor& c2) const {\n        return c2.r == r && c2.g == g && c2.b == b && c2.a == a;\n    }\n\n    // stubs for the AnimationMgr\n    CHyprColor operator-(const CHyprColor& c2) const {\n        RASSERT(false, \"CHyprColor: - is a STUB\");\n        return {};\n    }\n\n    CHyprColor operator+(const CHyprColor& c2) const {\n        RASSERT(false, \"CHyprColor: + is a STUB\");\n        return {};\n    }\n\n    CHyprColor operator*(const float& c2) const {\n        RASSERT(false, \"CHyprColor: * is a STUB\");\n        return {};\n    }\n\n    double r = 0, g = 0, b = 0, a = 0;\n\n  private:\n    Hyprgraphics::CColor::SOkLab m_okLab; // cache for the OkLab representation\n};\n\n//NOLINTNEXTLINE\nnamespace Colors {\n    static const CHyprColor WHITE      = CHyprColor(1.F, 1.F, 1.F, 1.F);\n    static const CHyprColor GREEN      = CHyprColor(0.F, 1.F, 0.F, 1.F);\n    static const CHyprColor BLUE       = CHyprColor(0.F, 0.F, 1.F, 1.F);\n    static const CHyprColor RED        = CHyprColor(1.F, 0.F, 0.F, 1.F);\n    static const CHyprColor ORANGE     = CHyprColor(1.F, 0.5F, 0.F, 1.F);\n    static const CHyprColor YELLOW     = CHyprColor(1.F, 1.F, 0.F, 1.F);\n    static const CHyprColor MAGENTA    = CHyprColor(1.F, 0.F, 1.F, 1.F);\n    static const CHyprColor PURPLE     = CHyprColor(0.5F, 0.F, 0.5F, 1.F);\n    static const CHyprColor LIME       = CHyprColor(0.5F, 1.F, 0.1F, 1.F);\n    static const CHyprColor LIGHT_BLUE = CHyprColor(0.1F, 1.F, 1.F, 1.F);\n    static const CHyprColor BLACK      = CHyprColor(0.F, 0.F, 0.F, 1.F);\n};\n"
  },
  {
    "path": "src/helpers/CursorShapes.hpp",
    "content": "#pragma once\n\n#include <array>\n\n// clang-format off\nconstexpr std::array<const char*, 37> CURSOR_SHAPE_NAMES = {\n    \"invalid\",\n    \"default\",\n    \"context-menu\",\n    \"help\",\n    \"pointer\",\n    \"progress\",\n    \"wait\",\n    \"cell\",\n    \"crosshair\",\n    \"text\",\n    \"vertical-text\",\n    \"alias\",\n    \"copy\",\n    \"move\",\n    \"no-drop\",\n    \"not-allowed\",\n    \"grab\",\n    \"grabbing\",\n    \"e-resize\",\n    \"n-resize\",\n    \"ne-resize\",\n    \"nw-resize\",\n    \"s-resize\",\n    \"se-resize\",\n    \"sw-resize\",\n    \"w-resize\",\n    \"ew-resize\",\n    \"ns-resize\",\n    \"nesw-resize\",\n    \"nwse-resize\",\n    \"col-resize\",\n    \"row-resize\",\n    \"all-scroll\",\n    \"zoom-in\",\n    \"zoom-out\",\n    \"dnd-ask\",\n    \"all-resize\"\n};\n// clang-format on\n"
  },
  {
    "path": "src/helpers/DamageRing.cpp",
    "content": "#include \"DamageRing.hpp\"\n\nvoid CDamageRing::setSize(const Vector2D& size_) {\n    if (size_ == m_size)\n        return;\n\n    m_size = size_;\n\n    damageEntire();\n}\n\nbool CDamageRing::damage(const CRegion& rg) {\n    CRegion clipped = rg.copy().intersect(CBox{{}, m_size});\n    if (clipped.empty())\n        return false;\n\n    m_current.add(clipped);\n    return true;\n}\n\nvoid CDamageRing::damageEntire() {\n    damage(CBox{{}, m_size});\n}\n\nvoid CDamageRing::rotate() {\n    m_previousIdx = (m_previousIdx + DAMAGE_RING_PREVIOUS_LEN - 1) % DAMAGE_RING_PREVIOUS_LEN;\n\n    m_previous[m_previousIdx] = m_current;\n    m_current.clear();\n}\n\nCRegion CDamageRing::getBufferDamage(int age) {\n    if (age <= 0 || age > DAMAGE_RING_PREVIOUS_LEN + 1)\n        return CBox{{}, m_size};\n\n    CRegion damage = m_current;\n\n    for (int i = 0; i < age - 1; ++i) {\n        int j = (m_previousIdx + i) % DAMAGE_RING_PREVIOUS_LEN;\n        damage.add(m_previous.at(j));\n    }\n\n    // don't return a ludicrous amount of rects\n    if (damage.getRects().size() > 8)\n        return damage.getExtents();\n\n    return damage;\n}\n\nbool CDamageRing::hasChanged() {\n    return !m_current.empty();\n}\n"
  },
  {
    "path": "src/helpers/DamageRing.hpp",
    "content": "#pragma once\n\n#include \"./math/Math.hpp\"\n#include <array>\n\nconstexpr static int DAMAGE_RING_PREVIOUS_LEN = 3;\n\nclass CDamageRing {\n  public:\n    void    setSize(const Vector2D& size_);\n    bool    damage(const CRegion& rg);\n    void    damageEntire();\n    void    rotate();\n    CRegion getBufferDamage(int age);\n    bool    hasChanged();\n\n  private:\n    Vector2D                                      m_size;\n    CRegion                                       m_current;\n    std::array<CRegion, DAMAGE_RING_PREVIOUS_LEN> m_previous;\n    size_t                                        m_previousIdx = 0;\n};\n"
  },
  {
    "path": "src/helpers/Drm.cpp",
    "content": "#include <xf86drm.h>\n#include \"Drm.hpp\"\n\nbool DRM::sameGpu(int fd1, int fd2) {\n    drmDevice* devA = nullptr;\n    drmDevice* devB = nullptr;\n\n    if (drmGetDevice2(fd1, 0, &devA) != 0)\n        return false;\n    if (drmGetDevice2(fd2, 0, &devB) != 0) {\n        drmFreeDevice(&devA);\n        return false;\n    }\n\n    bool same = drmDevicesEqual(devA, devB);\n\n    drmFreeDevice(&devA);\n    drmFreeDevice(&devB);\n    return same;\n}\n"
  },
  {
    "path": "src/helpers/Drm.hpp",
    "content": "#pragma once\n\nnamespace DRM {\n    bool sameGpu(int fd1, int fd2);\n}\n"
  },
  {
    "path": "src/helpers/Format.cpp",
    "content": "#include \"Format.hpp\"\n#include <vector>\n#include \"../includes.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"../macros.hpp\"\n#include <xf86drm.h>\n#include <drm_fourcc.h>\n\ninline const std::vector<SPixelFormat> GLES3_FORMATS = {\n    {\n        .drmFormat        = DRM_FORMAT_ARGB8888,\n        .glInternalFormat = GL_RGBA8,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_BYTE,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_XRGB8888,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_BGRA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_XRGB8888,\n        .glInternalFormat = GL_RGBA8,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_BYTE,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_XRGB8888,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_BGR1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_XBGR8888,\n        .glInternalFormat = GL_RGBA8,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_BYTE,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_XBGR8888,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_ABGR8888,\n        .glInternalFormat = GL_RGBA8,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_BYTE,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_XBGR8888,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_BGR888,\n        .glInternalFormat = GL_RGB8,\n        .glFormat         = GL_RGB,\n        .glType           = GL_UNSIGNED_BYTE,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_BGR888,\n        .bytesPerBlock    = 3,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_RGBX4444,\n        .glInternalFormat = GL_RGBA4,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_SHORT_4_4_4_4,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_RGBX4444,\n        .bytesPerBlock    = 2,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_RGBA4444,\n        .glInternalFormat = GL_RGBA4,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_SHORT_4_4_4_4,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_RGBX4444,\n        .bytesPerBlock    = 2,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_RGBX5551,\n        .glInternalFormat = GL_RGB5_A1,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_SHORT_5_5_5_1,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_RGBX5551,\n        .bytesPerBlock    = 2,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_RGBA5551,\n        .glInternalFormat = GL_RGB5_A1,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_SHORT_5_5_5_1,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_RGBX5551,\n        .bytesPerBlock    = 2,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_RGB565,\n        .glInternalFormat = GL_RGB565,\n        .glFormat         = GL_RGB,\n        .glType           = GL_UNSIGNED_SHORT_5_6_5,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_RGB565,\n        .bytesPerBlock    = 2,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_XBGR2101010,\n        .glInternalFormat = GL_RGB10_A2,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_INT_2_10_10_10_REV,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_XBGR2101010,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_ABGR2101010,\n        .glInternalFormat = GL_RGB10_A2,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_INT_2_10_10_10_REV,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_XBGR2101010,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_XRGB2101010,\n        .glInternalFormat = GL_RGB10_A2,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_INT_2_10_10_10_REV,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_XRGB2101010,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_BGR1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_ARGB2101010,\n        .glInternalFormat = GL_RGB10_A2,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_UNSIGNED_INT_2_10_10_10_REV,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_XRGB2101010,\n        .bytesPerBlock    = 4,\n        .swizzle          = {SWIZZLE_BGRA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_XBGR16161616F,\n        .glInternalFormat = GL_RGBA16F,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_HALF_FLOAT,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_XBGR16161616F,\n        .bytesPerBlock    = 8,\n        .swizzle          = {SWIZZLE_RGB1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_ABGR16161616F,\n        .glInternalFormat = GL_RGBA16F,\n        .glFormat         = GL_RGBA,\n        .glType           = GL_HALF_FLOAT,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_XBGR16161616F,\n        .bytesPerBlock    = 8,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_XBGR16161616,\n        .glInternalFormat = GL_RGBA16UI,\n        .glFormat         = GL_RGBA_INTEGER,\n        .glType           = GL_UNSIGNED_SHORT,\n        .withAlpha        = false,\n        .alphaStripped    = DRM_FORMAT_XBGR16161616,\n        .bytesPerBlock    = 8,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_ABGR16161616,\n        .glInternalFormat = GL_RGBA16UI,\n        .glFormat         = GL_RGBA_INTEGER,\n        .glType           = GL_UNSIGNED_SHORT,\n        .withAlpha        = true,\n        .alphaStripped    = DRM_FORMAT_XBGR16161616,\n        .bytesPerBlock    = 8,\n        .swizzle          = {SWIZZLE_RGBA},\n    },\n    {\n        .drmFormat     = DRM_FORMAT_YVYU,\n        .bytesPerBlock = 4,\n        .blockSize     = {2, 1},\n    },\n    {\n        .drmFormat     = DRM_FORMAT_VYUY,\n        .bytesPerBlock = 4,\n        .blockSize     = {2, 1},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_R8,\n        .glInternalFormat = GL_R8,\n        .glFormat         = GL_RED,\n        .glType           = GL_UNSIGNED_BYTE,\n        .bytesPerBlock    = 1,\n        .swizzle          = {SWIZZLE_R001},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_GR88,\n        .glInternalFormat = GL_RG8,\n        .glFormat         = GL_RG,\n        .glType           = GL_UNSIGNED_BYTE,\n        .bytesPerBlock    = 2,\n        .swizzle          = {SWIZZLE_RG01},\n    },\n    {\n        .drmFormat        = DRM_FORMAT_RGB888,\n        .glInternalFormat = GL_RGB8,\n        .glFormat         = GL_RGB,\n        .glType           = GL_UNSIGNED_BYTE,\n        .bytesPerBlock    = 3,\n        .swizzle          = {SWIZZLE_BGR1},\n    },\n};\n\nSHMFormat NFormatUtils::drmToShm(DRMFormat drm) {\n    switch (drm) {\n        case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888;\n        case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888;\n        default: return drm;\n    }\n\n    return drm;\n}\n\nDRMFormat NFormatUtils::shmToDRM(SHMFormat shm) {\n    switch (shm) {\n        case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888;\n        case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888;\n        default: return shm;\n    }\n\n    return shm;\n}\n\nconst SPixelFormat* NFormatUtils::getPixelFormatFromDRM(DRMFormat drm) {\n    for (auto const& fmt : GLES3_FORMATS) {\n        if (fmt.drmFormat == drm)\n            return &fmt;\n    }\n\n    return nullptr;\n}\n\nconst SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha) {\n    for (auto const& fmt : GLES3_FORMATS) {\n        if (fmt.glFormat == sc<int>(glFormat) && fmt.glType == sc<int>(glType) && fmt.withAlpha == alpha)\n            return &fmt;\n    }\n\n    return nullptr;\n}\n\nbool NFormatUtils::isFormatYUV(uint32_t drmFormat) {\n    switch (drmFormat) {\n        case DRM_FORMAT_YUYV:\n        case DRM_FORMAT_YVYU:\n        case DRM_FORMAT_UYVY:\n        case DRM_FORMAT_VYUY:\n        case DRM_FORMAT_AYUV:\n        case DRM_FORMAT_NV12:\n        case DRM_FORMAT_NV21:\n        case DRM_FORMAT_NV16:\n        case DRM_FORMAT_NV61:\n        case DRM_FORMAT_YUV410:\n        case DRM_FORMAT_YUV411:\n        case DRM_FORMAT_YUV420:\n        case DRM_FORMAT_YUV422:\n        case DRM_FORMAT_YUV444: return true;\n        default: return false;\n    }\n}\n\nbool NFormatUtils::isFormatOpaque(DRMFormat drm) {\n    const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm);\n    if (!FMT)\n        return false;\n\n    return !FMT->withAlpha;\n}\n\nint NFormatUtils::pixelsPerBlock(const SPixelFormat* const fmt) {\n    return fmt->blockSize.x * fmt->blockSize.y > 0 ? fmt->blockSize.x * fmt->blockSize.y : 1;\n}\n\nint NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) {\n    return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt));\n}\n\nstd::string NFormatUtils::drmFormatName(DRMFormat drm) {\n    auto n = drmGetFormatName(drm);\n\n    if (!n)\n        return \"unknown\";\n\n    std::string name = n;\n    free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)\n    return name;\n}\n\nstd::string NFormatUtils::drmModifierName(uint64_t mod) {\n    auto n = drmGetFormatModifierName(mod);\n\n    if (!n)\n        return \"unknown\";\n\n    std::string name = n;\n    free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)\n    return name;\n}\n\nDRMFormat NFormatUtils::alphaFormat(DRMFormat prevFormat) {\n    switch (prevFormat) {\n        case DRM_FORMAT_XRGB8888: return DRM_FORMAT_ARGB8888;\n        case DRM_FORMAT_XBGR8888: return DRM_FORMAT_ABGR8888;\n        case DRM_FORMAT_BGRX8888: return DRM_FORMAT_BGRA8888;\n        case DRM_FORMAT_RGBX8888: return DRM_FORMAT_RGBA8888;\n        case DRM_FORMAT_XRGB2101010: return DRM_FORMAT_ARGB2101010;\n        case DRM_FORMAT_XBGR2101010: return DRM_FORMAT_ABGR2101010;\n        case DRM_FORMAT_RGBX1010102: return DRM_FORMAT_RGBA1010102;\n        case DRM_FORMAT_BGRX1010102: return DRM_FORMAT_BGRA1010102;\n        default: return 0;\n    }\n}\n"
  },
  {
    "path": "src/helpers/Format.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <string>\n#include <GLES3/gl32.h>\n#include \"math/Math.hpp\"\n#include <aquamarine/backend/Misc.hpp>\n\nusing DRMFormat = uint32_t;\nusing SHMFormat = uint32_t;\n\n#define SWIZZLE_A1GB {GL_ALPHA, GL_ONE, GL_GREEN, GL_BLUE}\n#define SWIZZLE_ABG1 {GL_ALPHA, GL_BLUE, GL_GREEN, GL_ONE}\n#define SWIZZLE_ABGR {GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED}\n#define SWIZZLE_ARGB {GL_ALPHA, GL_RED, GL_GREEN, GL_BLUE}\n#define SWIZZLE_B1RG {GL_BLUE, GL_ONE, GL_RED, GL_GREEN}\n#define SWIZZLE_BARG {GL_BLUE, GL_ALPHA, GL_RED, GL_GREEN}\n#define SWIZZLE_BGR1 {GL_BLUE, GL_GREEN, GL_RED, GL_ONE}\n#define SWIZZLE_BGRA {GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA}\n#define SWIZZLE_G1AB {GL_GREEN, GL_ONE, GL_ALPHA, GL_BLUE}\n#define SWIZZLE_GBA1 {GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE}\n#define SWIZZLE_GBAR {GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED}\n#define SWIZZLE_GRAB {GL_GREEN, GL_RED, GL_ALPHA, GL_BLUE}\n#define SWIZZLE_R001 {GL_RED, GL_ZERO, GL_ZERO, GL_ONE}\n#define SWIZZLE_R1BG {GL_RED, GL_ONE, GL_BLUE, GL_GREEN}\n#define SWIZZLE_RABG {GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN}\n#define SWIZZLE_RG01 {GL_RED, GL_GREEN, GL_ZERO, GL_ONE}\n#define SWIZZLE_GR01 {GL_GREEN, GL_RED, GL_ZERO, GL_ONE}\n#define SWIZZLE_RGB1 {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}\n#define SWIZZLE_RGBA {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}\n\nstruct SPixelFormat {\n    DRMFormat                           drmFormat        = 0; /* DRM_FORMAT_INVALID */\n    int                                 glInternalFormat = 0;\n    int                                 glFormat         = 0;\n    int                                 glType           = 0;\n    bool                                withAlpha        = true;\n    DRMFormat                           alphaStripped    = 0; /* DRM_FORMAT_INVALID */\n    uint32_t                            bytesPerBlock    = 0;\n    Vector2D                            blockSize;\n    std::optional<std::array<GLint, 4>> swizzle = std::nullopt;\n};\n\nusing SDRMFormat = Aquamarine::SDRMFormat;\n\nnamespace NFormatUtils {\n    SHMFormat           drmToShm(DRMFormat drm);\n    DRMFormat           shmToDRM(SHMFormat shm);\n\n    const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm);\n    const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha);\n    bool                isFormatYUV(uint32_t drmFormat);\n    bool                isFormatOpaque(DRMFormat drm);\n    int                 pixelsPerBlock(const SPixelFormat* const fmt);\n    int                 minStride(const SPixelFormat* const fmt, int32_t width);\n    std::string         drmFormatName(DRMFormat drm);\n    std::string         drmModifierName(uint64_t mod);\n    DRMFormat           alphaFormat(DRMFormat prevFormat);\n};\n"
  },
  {
    "path": "src/helpers/MainLoopExecutor.cpp",
    "content": "#include \"MainLoopExecutor.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../macros.hpp\"\n#include <unistd.h>\n\nstatic int onDataRead(int fd, uint32_t mask, void* data) {\n    ((CMainLoopExecutor*)data)->onFired();\n    return 0;\n}\n\nCMainLoopExecutor::CMainLoopExecutor(std::function<void()>&& callback) : m_fn(std::move(callback)) {\n\n    int fds[2];\n    RASSERT(pipe(fds) == 0, \"CMainLoopExecutor: failed to open a pipe\");\n\n    m_event = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, fds[0], WL_EVENT_READABLE, ::onDataRead, this);\n\n    m_readFd  = Hyprutils::OS::CFileDescriptor(fds[0]);\n    m_writeFd = Hyprutils::OS::CFileDescriptor(fds[1]);\n}\n\nCMainLoopExecutor::~CMainLoopExecutor() {\n    if (m_event) // FIXME: potential race in case of a weird destroy on a worker thread\n        wl_event_source_remove(m_event);\n}\n\nvoid CMainLoopExecutor::signal() {\n    const char* amogus = \"h\";\n    write(m_writeFd.get(), amogus, 1);\n}\n\nvoid CMainLoopExecutor::onFired() {\n    if (!m_fn)\n        return;\n\n    m_fn();\n    m_fn = nullptr;\n\n    // we need to remove the event here because we're on the main thread\n    wl_event_source_remove(m_event);\n    m_event = nullptr;\n\n    m_readFd.reset();\n    m_writeFd.reset();\n}\n"
  },
  {
    "path": "src/helpers/MainLoopExecutor.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <wayland-server-core.h>\n\nclass CMainLoopExecutor {\n  public:\n    /*\n        MainLoopExecutor\n\n        Executes a function on the main thread once the writeFd() has some data written to it,\n        then destroys itself.\n\n        Needs to be kept owned, otherwise will die and kill the fds.\n    */\n\n    CMainLoopExecutor(std::function<void()>&& callback);\n    ~CMainLoopExecutor();\n\n    // Call from your worker thread: signals to the main thread. Destroy afterwards.\n    void signal();\n\n    // do not call\n    void onFired();\n\n  private:\n    Hyprutils::OS::CFileDescriptor m_readFd, m_writeFd;\n    wl_event_source*               m_event = nullptr;\n    std::function<void()>          m_fn;\n};\n"
  },
  {
    "path": "src/helpers/MiscFunctions.cpp",
    "content": "#include \"MiscFunctions.hpp\"\n#include \"../defines.hpp\"\n#include <algorithm>\n#include \"../Compositor.hpp\"\n#include \"../managers/TokenManager.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../desktop/history/WorkspaceHistoryTracker.hpp\"\n#include \"Monitor.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"fs/FsUtils.hpp\"\n#include <optional>\n#include <cstring>\n#include <climits>\n#include <cmath>\n#include <filesystem>\n#include <set>\n#include <sys/utsname.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <iomanip>\n#include <sstream>\n#include <fstream>\n#ifdef HAS_EXECINFO\n#include <execinfo.h>\n#endif\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/os/Process.hpp>\n#include \"../version.h\"\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::OS;\n\n#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <sys/sysctl.h>\n#if defined(__DragonFly__)\n#include <sys/kinfo.h> // struct kinfo_proc\n#elif defined(__FreeBSD__)\n#include <sys/user.h> // struct kinfo_proc\n#endif\n\n#if defined(__NetBSD__)\n#undef KERN_PROC\n#define KERN_PROC  KERN_PROC2\n#define KINFO_PROC struct kinfo_proc2\n#else\n#define KINFO_PROC struct kinfo_proc\n#endif\n#if defined(__DragonFly__)\n#define KP_PPID(kp) kp.kp_ppid\n#elif defined(__FreeBSD__)\n#define KP_PPID(kp) kp.ki_ppid\n#else\n#define KP_PPID(kp) kp.p_ppid\n#endif\n#endif\n\nstd::string absolutePath(const std::string& rawpath, const std::string& currentPath) {\n    auto value = rawpath;\n\n    if (value[0] == '~') {\n        static const char* const ENVHOME = getenv(\"HOME\");\n        value.replace(0, 1, std::string(ENVHOME));\n    } else if (value[0] != '/') {\n        auto currentDir = currentPath.substr(0, currentPath.find_last_of('/'));\n\n        if (value[0] == '.') {\n            if (value[1] == '.' && value[2] == '/') {\n                auto parentDir = currentDir.substr(0, currentDir.find_last_of('/'));\n                value.replace(0, 2 + currentPath.empty(), parentDir);\n            } else if (value[1] == '/')\n                value.replace(0, 1 + currentPath.empty(), currentDir);\n            else\n                value = currentDir + '/' + value;\n        } else\n            value = currentDir + '/' + value;\n    }\n\n    return value;\n}\n\nstd::string escapeJSONStrings(const std::string& str) {\n    std::ostringstream oss;\n    for (auto const& c : str) {\n        switch (c) {\n            case '\"': oss << \"\\\\\\\"\"; break;\n            case '\\\\': oss << \"\\\\\\\\\"; break;\n            case '\\b': oss << \"\\\\b\"; break;\n            case '\\f': oss << \"\\\\f\"; break;\n            case '\\n': oss << \"\\\\n\"; break;\n            case '\\r': oss << \"\\\\r\"; break;\n            case '\\t': oss << \"\\\\t\"; break;\n            default:\n                if ('\\x00' <= c && c <= '\\x1f') {\n                    oss << \"\\\\u\" << std::hex << std::setw(4) << std::setfill('0') << sc<int>(c);\n                } else {\n                    oss << c;\n                }\n        }\n    }\n    return oss.str();\n}\n\nstd::optional<float> getPlusMinusKeywordResult(std::string source, float relative) {\n    try {\n        return relative + stof(source);\n    } catch (...) {\n        Log::logger->log(Log::ERR, \"Invalid arg \\\"{}\\\" in getPlusMinusKeywordResult!\", source);\n        return {};\n    }\n}\n\nbool isDirection(const std::string& arg) {\n    return arg == \"l\" || arg == \"r\" || arg == \"u\" || arg == \"d\" || arg == \"t\" || arg == \"b\";\n}\n\nbool isDirection(const char& arg) {\n    return arg == 'l' || arg == 'r' || arg == 'u' || arg == 'd' || arg == 't' || arg == 'b';\n}\n\nstatic bool isAutoIDdWorkspace(WORKSPACEID id) {\n    return id < WORKSPACE_INVALID;\n}\n\nSWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {\n    SWorkspaceIDName result = {WORKSPACE_INVALID, \"\"};\n\n    if (in.starts_with(\"special\")) {\n        result.name = \"special:special\";\n\n        if (in.length() > 8) {\n            const auto NAME = in.substr(8);\n            const auto WS   = g_pCompositor->getWorkspaceByName(\"special:\" + NAME);\n\n            return {WS ? WS->m_id : g_pCompositor->getNewSpecialID(), \"special:\" + NAME};\n        }\n\n        result.id = SPECIAL_WORKSPACE_START;\n        return result;\n    } else if (in.starts_with(\"name:\")) {\n        const auto WORKSPACENAME = in.substr(in.find_first_of(':') + 1);\n        const auto WORKSPACE     = g_pCompositor->getWorkspaceByName(WORKSPACENAME);\n        if (!WORKSPACE) {\n            result.id = g_pCompositor->getNextAvailableNamedWorkspace();\n        } else {\n            result.id = WORKSPACE->m_id;\n        }\n        result.name = WORKSPACENAME;\n    } else if (in.starts_with(\"empty\")) {\n        const bool same_mon = in.substr(5).contains(\"m\");\n        const bool next     = in.substr(5).contains(\"n\");\n        if ((same_mon || next) && !Desktop::focusState()->monitor()) {\n            Log::logger->log(Log::ERR, \"Empty monitor workspace on monitor null!\");\n            return {WORKSPACE_INVALID};\n        }\n\n        std::set<WORKSPACEID> invalidWSes;\n        if (same_mon) {\n            for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) {\n                const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.monitor);\n                if (PMONITOR && (PMONITOR->m_id != Desktop::focusState()->monitor()->m_id))\n                    invalidWSes.insert(rule.workspaceId);\n            }\n        }\n\n        WORKSPACEID id = next ? Desktop::focusState()->monitor()->activeWorkspaceID() : 0;\n        while (++id < LONG_MAX) {\n            const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(id);\n            if (!invalidWSes.contains(id) && (!PWORKSPACE || PWORKSPACE->getWindows() == 0)) {\n                result.id = id;\n                return result;\n            }\n        }\n    } else if (in.starts_with(\"prev\")) {\n        if (!Desktop::focusState()->monitor())\n            return {WORKSPACE_INVALID};\n\n        const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;\n\n        if (!valid(PWORKSPACE))\n            return {WORKSPACE_INVALID};\n\n        const auto PREVWORKSPACEIDNAME = Desktop::History::workspaceTracker()->previousWorkspaceIDName(PWORKSPACE);\n\n        if (PREVWORKSPACEIDNAME.id == -1)\n            return {WORKSPACE_INVALID};\n\n        const auto PLASTWORKSPACE = g_pCompositor->getWorkspaceByID(PREVWORKSPACEIDNAME.id);\n\n        if (!PLASTWORKSPACE) {\n            Log::logger->log(Log::DEBUG, \"previous workspace {} doesn't exist yet\", PREVWORKSPACEIDNAME.id);\n            return {PREVWORKSPACEIDNAME.id, PREVWORKSPACEIDNAME.name};\n        }\n\n        return {PLASTWORKSPACE->m_id, PLASTWORKSPACE->m_name};\n    } else if (in == \"next\") {\n        if (!Desktop::focusState()->monitor() || !Desktop::focusState()->monitor()->m_activeWorkspace) {\n            Log::logger->log(Log::ERR, \"no active monitor or workspace for 'next'\");\n            return {WORKSPACE_INVALID};\n        }\n\n        auto        PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;\n\n        WORKSPACEID nextId = PCURRENTWORKSPACE->m_id + 1;\n\n        if (nextId <= 0)\n            return {WORKSPACE_INVALID};\n\n        result.id   = nextId;\n        result.name = std::to_string(nextId);\n        return result;\n    } else {\n        if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {\n            bool absolute = in[1] == '~';\n            if (!Desktop::focusState()->monitor()) {\n                Log::logger->log(Log::ERR, \"Relative monitor workspace on monitor null!\");\n                return {WORKSPACE_INVALID};\n            }\n\n            const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(absolute ? 2 : 1), 0);\n\n            if (!PLUSMINUSRESULT.has_value())\n                return {WORKSPACE_INVALID};\n\n            result.id = sc<int>(PLUSMINUSRESULT.value());\n\n            WORKSPACEID           remains = result.id;\n\n            std::set<WORKSPACEID> invalidWSes;\n\n            // Collect all the workspaces we can't jump to.\n            for (auto const& ws : g_pCompositor->getWorkspaces()) {\n                if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor())) {\n                    // Can't jump to this workspace\n                    invalidWSes.insert(ws->m_id);\n                }\n            }\n            for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) {\n                const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.monitor);\n                if (!PMONITOR || PMONITOR->m_id == Desktop::focusState()->monitor()->m_id) {\n                    // Can't be invalid\n                    continue;\n                }\n                // WS is bound to another monitor, can't jump to this\n                invalidWSes.insert(rule.workspaceId);\n            }\n\n            // Prepare all named workspaces in case when we need them\n            std::vector<WORKSPACEID> namedWSes;\n            for (auto const& ws : g_pCompositor->getWorkspaces()) {\n                if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor()) || ws->m_id >= 0)\n                    continue;\n\n                namedWSes.push_back(ws->m_id);\n            }\n            std::ranges::sort(namedWSes);\n\n            if (absolute) {\n                // 1-index\n                remains -= 1;\n\n                // traverse valid workspaces until we reach the remains\n                if (sc<size_t>(remains) < namedWSes.size()) {\n                    result.id = namedWSes[remains];\n                } else {\n                    remains -= namedWSes.size();\n                    result.id = 0;\n                    while (remains >= 0) {\n                        result.id++;\n                        if (!invalidWSes.contains(result.id)) {\n                            remains--;\n                        }\n                    }\n                }\n            } else {\n\n                // Just take a blind guess at where we'll probably end up\n                WORKSPACEID activeWSID    = Desktop::focusState()->monitor()->m_activeWorkspace ? Desktop::focusState()->monitor()->m_activeWorkspace->m_id : 1;\n                WORKSPACEID predictedWSID = activeWSID + remains;\n                int         remainingWSes = 0;\n                char        walkDir       = in[1];\n\n                // sanitize. 0 means invalid oob in -\n                predictedWSID = std::max(predictedWSID, sc<int64_t>(0));\n\n                // Count how many invalidWSes are in between (how bad the prediction was)\n                WORKSPACEID beginID = in[1] == '+' ? activeWSID + 1 : predictedWSID;\n                WORKSPACEID endID   = in[1] == '+' ? predictedWSID : activeWSID;\n                auto        begin   = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >=\n                for (auto it = begin; it != invalidWSes.end() && *it <= endID; it++) {\n                    remainingWSes++;\n                }\n\n                // Handle named workspaces. They are treated like always before other workspaces\n                if (activeWSID < 0) {\n                    // Behaviour similar to 'm'\n                    // Find current\n                    size_t currentItem = -1;\n                    for (size_t i = 0; i < namedWSes.size(); i++) {\n                        if (namedWSes[i] == activeWSID) {\n                            currentItem = i;\n                            break;\n                        }\n                    }\n\n                    currentItem += remains;\n                    currentItem = std::max(currentItem, sc<size_t>(0));\n                    if (currentItem >= namedWSes.size()) {\n                        // At the seam between namedWSes and normal WSes. Behave like r+[diff] at imaginary ws 0\n                        size_t diff         = currentItem - (namedWSes.size() - 1);\n                        predictedWSID       = diff;\n                        WORKSPACEID beginID = 1;\n                        WORKSPACEID endID   = predictedWSID;\n                        auto        begin   = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >=\n                        for (auto it = begin; it != invalidWSes.end() && *it <= endID; it++) {\n                            remainingWSes++;\n                        }\n                        walkDir = '+';\n                    } else {\n                        // We found our final ws.\n                        remainingWSes = 0;\n                        predictedWSID = namedWSes[currentItem];\n                    }\n                }\n\n                // Go in the search direction for remainingWSes\n                // The performance impact is directly proportional to the number of open and bound workspaces\n                WORKSPACEID finalWSID = predictedWSID;\n                if (walkDir == '-') {\n                    WORKSPACEID beginID = finalWSID;\n                    WORKSPACEID curID   = finalWSID;\n                    while (--curID > 0 && remainingWSes > 0) {\n                        if (!invalidWSes.contains(curID)) {\n                            remainingWSes--;\n                        }\n                        finalWSID = curID;\n                    }\n                    if (finalWSID <= 0 || invalidWSes.contains(finalWSID)) {\n                        if (!namedWSes.empty()) {\n                            // Go to the named workspaces\n                            // Need remainingWSes more\n                            auto namedWSIdx = namedWSes.size() - remainingWSes;\n                            // Sanitze\n                            namedWSIdx = std::clamp(namedWSIdx, sc<size_t>(0), namedWSes.size() - sc<size_t>(1));\n                            finalWSID  = namedWSes[namedWSIdx];\n                        } else {\n                            // Couldn't find valid workspace in negative direction, search last first one back up positive direction\n                            walkDir = '+';\n                            // We know, that everything less than beginID is invalid, so don't bother with that\n                            finalWSID     = beginID;\n                            remainingWSes = 1;\n                        }\n                    }\n                }\n                if (walkDir == '+') {\n                    WORKSPACEID curID = finalWSID;\n                    while (++curID < INT32_MAX && remainingWSes > 0) {\n                        if (!invalidWSes.contains(curID)) {\n                            remainingWSes--;\n                        }\n                        finalWSID = curID;\n                    }\n                }\n                result.id = finalWSID;\n            }\n\n            const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(result.id);\n            if (PWORKSPACE)\n                result.name = g_pCompositor->getWorkspaceByID(result.id)->m_name;\n            else\n                result.name = std::to_string(result.id);\n\n        } else if ((in[0] == 'm' || in[0] == 'e') && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {\n            bool onAllMonitors = in[0] == 'e';\n            bool absolute      = in[1] == '~';\n\n            if (!Desktop::focusState()->monitor()) {\n                Log::logger->log(Log::ERR, \"Relative monitor workspace on monitor null!\");\n                return {WORKSPACE_INVALID};\n            }\n\n            // monitor relative\n            const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(absolute ? 2 : 1), 0);\n\n            if (!PLUSMINUSRESULT.has_value())\n                return {WORKSPACE_INVALID};\n\n            result.id = sc<int>(PLUSMINUSRESULT.value());\n\n            // result now has +/- what we should move on mon\n            int                      remains = sc<int>(result.id);\n\n            std::vector<WORKSPACEID> validWSes;\n            for (auto const& ws : g_pCompositor->getWorkspaces()) {\n                if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor() && !onAllMonitors))\n                    continue;\n\n                validWSes.push_back(ws->m_id);\n            }\n\n            std::ranges::sort(validWSes);\n\n            ssize_t currentItem = -1;\n\n            if (absolute) {\n                // 1-index\n                currentItem = remains - 1;\n\n                // clamp\n                if (currentItem < 0) {\n                    currentItem = 0;\n                } else if (currentItem >= sc<ssize_t>(validWSes.size())) {\n                    currentItem = validWSes.size() - 1;\n                }\n            } else {\n                // get the offset\n                remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size();\n\n                // get the current item\n                WORKSPACEID activeWSID = Desktop::focusState()->monitor()->m_activeWorkspace ? Desktop::focusState()->monitor()->m_activeWorkspace->m_id : 1;\n                for (ssize_t i = 0; i < sc<ssize_t>(validWSes.size()); i++) {\n                    if (validWSes[i] == activeWSID) {\n                        currentItem = i;\n                        break;\n                    }\n                }\n\n                // apply\n                currentItem += remains;\n\n                // sanitize\n                if (currentItem >= sc<ssize_t>(validWSes.size())) {\n                    currentItem = currentItem % validWSes.size();\n                } else if (currentItem < 0) {\n                    currentItem = validWSes.size() + currentItem;\n                }\n            }\n\n            result.id   = validWSes[currentItem];\n            result.name = g_pCompositor->getWorkspaceByID(validWSes[currentItem])->m_name;\n        } else {\n            if (in[0] == '+' || in[0] == '-') {\n                if (Desktop::focusState()->monitor()) {\n                    const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, Desktop::focusState()->monitor()->activeWorkspaceID());\n                    if (!PLUSMINUSRESULT.has_value())\n                        return {WORKSPACE_INVALID};\n\n                    result.id = std::max(sc<int>(PLUSMINUSRESULT.value()), 1);\n                } else {\n                    Log::logger->log(Log::ERR, \"Relative workspace on no mon!\");\n                    return {WORKSPACE_INVALID};\n                }\n            } else if (isNumber(in))\n                result.id = std::max(std::stoi(in), 1);\n            else {\n                // maybe name\n                const auto PWORKSPACE = g_pCompositor->getWorkspaceByName(in);\n                if (PWORKSPACE)\n                    result.id = PWORKSPACE->m_id;\n            }\n\n            result.name = std::to_string(result.id);\n        }\n    }\n\n    result.isAutoIDd = isAutoIDdWorkspace(result.id);\n\n    return result;\n}\n\nstd::optional<std::string> cleanCmdForWorkspace(const std::string& inWorkspaceName, std::string dirtyCmd) {\n\n    std::string cmd = trim(dirtyCmd);\n\n    if (!cmd.empty()) {\n        std::string       rules;\n        const std::string workspaceRule = \"workspace \" + inWorkspaceName;\n\n        if (cmd[0] == '[') {\n            const auto closingBracketIdx = cmd.find_last_of(']');\n            auto       tmpRules          = cmd.substr(1, closingBracketIdx - 1);\n            cmd                          = cmd.substr(closingBracketIdx + 1);\n\n            auto rulesList = CVarList(tmpRules, 0, ';');\n\n            bool hadWorkspaceRule = false;\n            rulesList.map([&](std::string& rule) {\n                if (rule.find(\"workspace\") == 0) {\n                    rule             = workspaceRule;\n                    hadWorkspaceRule = true;\n                }\n            });\n\n            if (!hadWorkspaceRule)\n                rulesList.append(workspaceRule);\n\n            rules = \"[\" + rulesList.join(\";\") + \"]\";\n        } else {\n            rules = \"[\" + workspaceRule + \"]\";\n        }\n\n        return std::optional<std::string>(rules + \" \" + cmd);\n    }\n\n    return std::nullopt;\n}\n\nfloat vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2) {\n    const float DX = std::max({0.0, p1.x - vec.x, vec.x - p2.x});\n    const float DY = std::max({0.0, p1.y - vec.y, vec.y - p2.y});\n    return DX * DX + DY * DY;\n}\n\n// Execute a shell command and get the output\nstd::string execAndGet(const char* cmd) {\n    CProcess proc(\"/bin/sh\", {\"-c\", cmd});\n\n    if (!proc.runSync())\n        return \"error\";\n\n    return proc.stdOut();\n}\n\nvoid logSystemInfo() {\n    struct utsname unameInfo;\n\n    uname(&unameInfo);\n\n    Log::logger->log(Log::DEBUG, \"System name: {}\", std::string{unameInfo.sysname});\n    Log::logger->log(Log::DEBUG, \"Node name: {}\", std::string{unameInfo.nodename});\n    Log::logger->log(Log::DEBUG, \"Release: {}\", std::string{unameInfo.release});\n    Log::logger->log(Log::DEBUG, \"Version: {}\", std::string{unameInfo.version});\n\n    Log::logger->log(Log::DEBUG, \"\\n\");\n\n#if defined(__DragonFly__) || defined(__FreeBSD__)\n    const std::string GPUINFO = execAndGet(\"pciconf -lv | grep -F -A4 vga\");\n#elif defined(__arm__) || defined(__aarch64__)\n    std::string                 GPUINFO;\n    const std::filesystem::path dev_tree = \"/proc/device-tree\";\n    try {\n        if (std::filesystem::exists(dev_tree) && std::filesystem::is_directory(dev_tree)) {\n            std::for_each(std::filesystem::directory_iterator(dev_tree), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& entry) {\n                if (std::filesystem::is_directory(entry) && entry.path().filename().string().starts_with(\"soc\")) {\n                    std::for_each(std::filesystem::directory_iterator(entry.path()), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& sub_entry) {\n                        if (std::filesystem::is_directory(sub_entry) && sub_entry.path().filename().string().starts_with(\"gpu\")) {\n                            std::filesystem::path file_path = sub_entry.path() / \"compatible\";\n                            std::ifstream         file(file_path);\n                            if (file)\n                                GPUINFO.append(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());\n                        }\n                    });\n                }\n            });\n        }\n    } catch (...) { GPUINFO = \"error\"; }\n#else\n    const std::string GPUINFO = execAndGet(\"lspci -vnn | grep -E '(VGA|Display|3D)'\");\n#endif\n    Log::logger->log(Log::DEBUG, \"GPU information:\\n{}\\n\", GPUINFO);\n\n    if (GPUINFO.contains(\"NVIDIA\")) {\n        Log::logger->log(Log::WARN, \"Warning: you're using an NVIDIA GPU. Make sure you follow the instructions on the wiki if anything is amiss.\\n\");\n    }\n\n    // log etc\n    Log::logger->log(Log::DEBUG, \"os-release:\");\n\n    Log::logger->log(Log::DEBUG, \"{}\", NFsUtils::readFileAsString(\"/etc/os-release\").value_or(\"error\"));\n}\n\nint64_t getPPIDof(int64_t pid) {\n#if defined(KERN_PROC_PID)\n    int mib[] = {\n        CTL_KERN,           KERN_PROC, KERN_PROC_PID, (int)pid,\n#if defined(__NetBSD__) || defined(__OpenBSD__)\n        sizeof(KINFO_PROC), 1,\n#endif\n    };\n    u_int      miblen = sizeof(mib) / sizeof(mib[0]);\n    KINFO_PROC kp;\n    size_t     sz = sizeof(KINFO_PROC);\n    if (sysctl(mib, miblen, &kp, &sz, NULL, 0) != -1)\n        return KP_PPID(kp);\n\n    return 0;\n#else\n    std::string dir = \"/proc/\" + std::to_string(pid) + \"/status\";\n    FILE*       infile;\n\n    infile = fopen(dir.c_str(), \"r\");\n    if (!infile)\n        return 0;\n\n    char*       line = nullptr;\n    size_t      len  = 0;\n    ssize_t     len2 = 0;\n\n    std::string pidstr;\n\n    while ((len2 = getline(&line, &len, infile)) != -1) {\n        if (strstr(line, \"PPid:\")) {\n            pidstr            = std::string(line, len2);\n            const auto tabpos = pidstr.find_last_of('\\t');\n            if (tabpos != std::string::npos)\n                pidstr = pidstr.substr(tabpos);\n            break;\n        }\n    }\n\n    fclose(infile);\n    if (line)\n        free(line); // NOLINT(cppcoreguidelines-no-malloc)\n\n    try {\n        return std::stoll(pidstr);\n    } catch (std::exception& e) { return 0; }\n#endif\n}\n\nstd::expected<int64_t, std::string> configStringToInt(const std::string& VALUE) {\n    auto parseHex = [](const std::string& value) -> std::expected<int64_t, std::string> {\n        try {\n            size_t position;\n            auto   result = stoll(value, &position, 16);\n            if (position == value.size())\n                return result;\n        } catch (const std::exception&) {}\n        return std::unexpected(\"invalid hex \" + value);\n    };\n    if (VALUE.starts_with(\"0x\")) {\n        // Values with 0x are hex\n        return parseHex(VALUE);\n    } else if (VALUE.starts_with(\"rgba(\") && VALUE.ends_with(')')) {\n        const auto VALUEWITHOUTFUNC = trim(VALUE.substr(5, VALUE.length() - 6));\n\n        // try doing it the comma way first\n        if (std::ranges::count(VALUEWITHOUTFUNC, ',') == 3) {\n            // cool\n            std::string rolling = VALUEWITHOUTFUNC;\n            auto        r       = configStringToInt(trim(rolling.substr(0, rolling.find(','))));\n            rolling             = rolling.substr(rolling.find(',') + 1);\n            auto g              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));\n            rolling             = rolling.substr(rolling.find(',') + 1);\n            auto b              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));\n            rolling             = rolling.substr(rolling.find(',') + 1);\n            uint8_t a           = 0;\n\n            if (!r || !g || !b)\n                return std::unexpected(\"failed parsing \" + VALUEWITHOUTFUNC);\n\n            try {\n                a = std::round(std::stof(trim(rolling.substr(0, rolling.find(',')))) * 255.f);\n            } catch (std::exception& e) { return std::unexpected(\"failed parsing \" + VALUEWITHOUTFUNC); }\n\n            return a * sc<Hyprlang::INT>(0x1000000) + *r * sc<Hyprlang::INT>(0x10000) + *g * sc<Hyprlang::INT>(0x100) + *b;\n        } else if (VALUEWITHOUTFUNC.length() == 8) {\n            const auto RGBA = parseHex(VALUEWITHOUTFUNC);\n\n            if (!RGBA)\n                return RGBA;\n            // now we need to RGBA -> ARGB. The config holds ARGB only.\n            return (*RGBA >> 8) + 0x1000000 * (*RGBA & 0xFF);\n        }\n\n        return std::unexpected(\"rgba() expects length of 8 characters (4 bytes) or 4 comma separated values\");\n\n    } else if (VALUE.starts_with(\"rgb(\") && VALUE.ends_with(')')) {\n        const auto VALUEWITHOUTFUNC = trim(VALUE.substr(4, VALUE.length() - 5));\n\n        // try doing it the comma way first\n        if (std::ranges::count(VALUEWITHOUTFUNC, ',') == 2) {\n            // cool\n            std::string rolling = VALUEWITHOUTFUNC;\n            auto        r       = configStringToInt(trim(rolling.substr(0, rolling.find(','))));\n            rolling             = rolling.substr(rolling.find(',') + 1);\n            auto g              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));\n            rolling             = rolling.substr(rolling.find(',') + 1);\n            auto b              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));\n\n            if (!r || !g || !b)\n                return std::unexpected(\"failed parsing \" + VALUEWITHOUTFUNC);\n\n            return sc<Hyprlang::INT>(0xFF000000) + *r * sc<Hyprlang::INT>(0x10000) + *g * sc<Hyprlang::INT>(0x100) + *b;\n        } else if (VALUEWITHOUTFUNC.length() == 6) {\n            auto r = parseHex(VALUEWITHOUTFUNC);\n            return r ? *r + 0xFF000000 : r;\n        }\n\n        return std::unexpected(\"rgb() expects length of 6 characters (3 bytes) or 3 comma separated values\");\n    } else if (VALUE.starts_with(\"true\") || VALUE.starts_with(\"on\") || VALUE.starts_with(\"yes\")) {\n        return 1;\n    } else if (VALUE.starts_with(\"false\") || VALUE.starts_with(\"off\") || VALUE.starts_with(\"no\")) {\n        return 0;\n    }\n\n    if (VALUE.empty() || !isNumber(VALUE, false))\n        return std::unexpected(\"cannot parse \\\"\" + VALUE + \"\\\" as an int.\");\n\n    try {\n        const auto RES = std::stoll(VALUE);\n        return RES;\n    } catch (std::exception& e) { return std::unexpected(std::string{\"stoll threw: \"} + e.what()); }\n\n    return std::unexpected(\"parse error\");\n}\n\nVector2D configStringToVector2D(const std::string& VALUE) {\n    std::istringstream iss(VALUE);\n    std::string        token;\n\n    if (!std::getline(iss, token, ' ') && !std::getline(iss, token, ','))\n        throw std::invalid_argument(\"Invalid string format\");\n\n    if (!isNumber(token))\n        throw std::invalid_argument(\"Invalid x value\");\n\n    long long x = std::stoll(token);\n\n    if (!std::getline(iss, token))\n        throw std::invalid_argument(\"Invalid string format\");\n\n    if (!isNumber(token))\n        throw std::invalid_argument(\"Invalid y value\");\n\n    long long y = std::stoll(token);\n\n    if (std::getline(iss, token))\n        throw std::invalid_argument(\"Invalid string format\");\n\n    return Vector2D(sc<double>(x), sc<double>(y));\n}\n\ndouble normalizeAngleRad(double ang) {\n    if (ang > M_PI * 2) {\n        while (ang > M_PI * 2)\n            ang -= M_PI * 2;\n        return ang;\n    }\n\n    if (ang < 0.0) {\n        while (ang < 0.0)\n            ang += M_PI * 2;\n        return ang;\n    }\n\n    return ang;\n}\n\nstd::vector<SCallstackFrameInfo> getBacktrace() {\n    std::vector<SCallstackFrameInfo> callstack;\n\n#ifdef HAS_EXECINFO\n    void*  bt[1024];\n    int    btSize;\n    char** btSymbols;\n\n    btSize    = backtrace(bt, 1024);\n    btSymbols = backtrace_symbols(bt, btSize);\n\n    for (auto i = 0; i < btSize; ++i) {\n        callstack.emplace_back(SCallstackFrameInfo{bt[i], std::string{btSymbols[i]}});\n    }\n#else\n    callstack.emplace_back(SCallstackFrameInfo{nullptr, \"configuration does not support execinfo.h\"});\n#endif\n\n    return callstack;\n}\n\nvoid throwError(const std::string& err) {\n    Log::logger->log(Log::CRIT, \"Critical error thrown: {}\", err);\n    throw std::runtime_error(err);\n}\n\nstd::pair<CFileDescriptor, std::string> openExclusiveShm() {\n    // Only absolute paths can be shared across different shm_open() calls\n    std::string name = \"/\" + g_pTokenManager->getRandomUUID();\n\n    for (size_t i = 0; i < 69; ++i) {\n        CFileDescriptor fd{shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)};\n        if (fd.isValid())\n            return {std::move(fd), name};\n    }\n\n    return {{}, \"\"};\n}\n\nCFileDescriptor allocateSHMFile(size_t len) {\n    auto [fd, name] = openExclusiveShm();\n    if (!fd.isValid())\n        return {};\n\n    shm_unlink(name.c_str());\n\n    int ret;\n    do {\n        ret = ftruncate(fd.get(), len);\n    } while (ret < 0 && errno == EINTR);\n\n    if (ret < 0) {\n        return {};\n    }\n\n    return std::move(fd);\n}\n\nbool allocateSHMFilePair(size_t size, CFileDescriptor& rw_fd_ptr, CFileDescriptor& ro_fd_ptr) {\n    auto [fd, name] = openExclusiveShm();\n    if (!fd.isValid()) {\n        return false;\n    }\n\n    // CLOEXEC is guaranteed to be set by shm_open\n    CFileDescriptor ro_fd{shm_open(name.c_str(), O_RDONLY, 0)};\n    if (!ro_fd.isValid()) {\n        shm_unlink(name.c_str());\n        return false;\n    }\n\n    shm_unlink(name.c_str());\n\n    // Make sure the file cannot be re-opened in read-write mode (e.g. via\n    // \"/proc/self/fd/\" on Linux)\n    if (fchmod(fd.get(), 0) != 0) {\n        return false;\n    }\n\n    int ret;\n    do {\n        ret = ftruncate(fd.get(), size);\n    } while (ret < 0 && errno == EINTR);\n    if (ret < 0) {\n        return false;\n    }\n\n    rw_fd_ptr = std::move(fd);\n    ro_fd_ptr = std::move(ro_fd);\n    return true;\n}\n\nfloat stringToPercentage(const std::string& VALUE, const float REL) {\n    if (VALUE.ends_with('%'))\n        return (std::stof(VALUE.substr(0, VALUE.length() - 1)) * REL) / 100.f;\n    else\n        return std::stof(VALUE);\n}\n\n// Checks if Nvidia driver major version is at least given version.\n// Useful for explicit_sync_kms and ctm_animation as they only work\n// past certain driver versions.\nbool isNvidiaDriverVersionAtLeast(int threshold) {\n    static int  driverMajor = 0;\n    static bool once        = true;\n\n    if (once) {\n        once = false;\n\n        std::error_code ec;\n        if (std::filesystem::exists(\"/sys/module/nvidia_drm/version\", ec) && !ec) {\n            std::ifstream ifs(\"/sys/module/nvidia_drm/version\");\n            if (ifs.good()) {\n                try {\n                    std::string driverInfo((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));\n\n                    size_t      firstDot = driverInfo.find('.');\n                    if (firstDot != std::string::npos)\n                        driverMajor = std::stoi(driverInfo.substr(0, firstDot));\n\n                    Log::logger->log(Log::DEBUG, \"Parsed NVIDIA major version: {}\", driverMajor);\n\n                } catch (std::exception& e) {\n                    driverMajor = 0; // Default to 0 if parsing fails\n                }\n\n                ifs.close();\n            }\n        }\n    }\n\n    return driverMajor >= threshold;\n}\n\nstd::expected<std::string, std::string> binaryNameForWlClient(wl_client* client) {\n    if (!client)\n        return std::unexpected(\"client unknown\");\n\n    pid_t pid = 0;\n    wl_client_get_credentials(client, &pid, nullptr, nullptr);\n\n    return binaryNameForPid(pid);\n}\n\nstd::expected<std::string, std::string> binaryNameForPid(pid_t pid) {\n    if (pid <= 0)\n        return std::unexpected(\"No pid for client\");\n\n#if defined(KERN_PROC_PATHNAME)\n    int mib[] = {\n        CTL_KERN,\n#if defined(__NetBSD__)\n        KERN_PROC_ARGS,\n        pid,\n        KERN_PROC_PATHNAME,\n#else\n        KERN_PROC,\n        KERN_PROC_PATHNAME,\n        pid,\n#endif\n    };\n    u_int  miblen        = sizeof(mib) / sizeof(mib[0]);\n    char   exe[PATH_MAX] = \"/nonexistent\";\n    size_t sz            = sizeof(exe);\n    sysctl(mib, miblen, &exe, &sz, NULL, 0);\n    std::string path = exe;\n#else\n    std::string path = std::format(\"/proc/{}/exe\", sc<uint64_t>(pid));\n#endif\n    std::error_code ec;\n\n    std::string     fullPath = std::filesystem::canonical(path, ec);\n\n    if (ec)\n        return std::unexpected(\"canonical failed\");\n\n    return fullPath;\n}\n\nstd::string deviceNameToInternalString(std::string in) {\n    std::ranges::replace(in, ' ', '-');\n    std::ranges::replace(in, '\\n', '-');\n    std::ranges::replace(in, ',', '-');\n    std::ranges::transform(in, in.begin(), ::tolower);\n    return in;\n}\n\nstatic const std::vector<const char*> PKGCONF_PATHS = {\"/usr/lib/pkgconfig\", \"/usr/local/lib/pkgconfig\"};\n\n//\nstd::string getSystemLibraryVersion(const std::string& name) {\n    for (const auto& pkgconf : PKGCONF_PATHS) {\n        std::error_code   ec;\n        const std::string PATH = std::string{pkgconf} + \"/\" + name + \".pc\";\n        if (!std::filesystem::exists(PATH, ec))\n            continue;\n\n        const auto DATA = NFsUtils::readFileAsString(PATH);\n\n        if (!DATA)\n            continue;\n\n        size_t versionAt    = DATA->find(\"\\nVersion: \");\n        size_t versionAtEnd = DATA->find(\"\\n\", versionAt + 11);\n\n        if (versionAt == std::string::npos)\n            continue;\n\n        versionAt += 10;\n\n        return DATA->substr(versionAt, versionAtEnd == std::string::npos ? std::string::npos : versionAtEnd - versionAt);\n    }\n    return \"unknown\";\n}\n\nstd::string getBuiltSystemLibraryNames() {\n    std::string result = \"Libraries:\\n\";\n    result += std::format(\"Hyprgraphics: built against {}, system has {}\\n\", HYPRGRAPHICS_VERSION, getSystemLibraryVersion(\"hyprgraphics\"));\n    result += std::format(\"Hyprutils: built against {}, system has {}\\n\", HYPRUTILS_VERSION, getSystemLibraryVersion(\"hyprutils\"));\n    result += std::format(\"Hyprcursor: built against {}, system has {}\\n\", HYPRCURSOR_VERSION, getSystemLibraryVersion(\"hyprcursor\"));\n    result += std::format(\"Hyprlang: built against {}, system has {}\\n\", HYPRLANG_VERSION, getSystemLibraryVersion(\"hyprlang\"));\n    result += std::format(\"Aquamarine: built against {}, system has {}\\n\", AQUAMARINE_VERSION, getSystemLibraryVersion(\"aquamarine\"));\n    return result;\n}\n\nbool truthy(const std::string& str) {\n    if (str == \"1\")\n        return true;\n\n    std::string cpy = str;\n    std::ranges::transform(cpy, cpy.begin(), ::tolower);\n\n    return cpy.starts_with(\"true\") || cpy.starts_with(\"yes\") || cpy.starts_with(\"on\");\n}\n"
  },
  {
    "path": "src/helpers/MiscFunctions.hpp",
    "content": "#pragma once\n\n#include <optional>\n#include <wayland-server.h>\n#include <vector>\n#include <format>\n#include <expected>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include \"../SharedDefs.hpp\"\n#include \"../macros.hpp\"\n\nstruct SCallstackFrameInfo {\n    void*       adr = nullptr;\n    std::string desc;\n};\n\nstruct SWorkspaceIDName {\n    WORKSPACEID id = WORKSPACE_INVALID;\n    std::string name;\n    bool        isAutoIDd = false;\n};\n\nstd::string                             absolutePath(const std::string&, const std::string&);\nstd::string                             escapeJSONStrings(const std::string& str);\nbool                                    isDirection(const std::string&);\nbool                                    isDirection(const char&);\nSWorkspaceIDName                        getWorkspaceIDNameFromString(const std::string&);\nstd::optional<std::string>              cleanCmdForWorkspace(const std::string&, std::string);\nfloat                                   vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2);\nvoid                                    logSystemInfo();\nstd::string                             execAndGet(const char*);\nint64_t                                 getPPIDof(int64_t pid);\nstd::expected<int64_t, std::string>     configStringToInt(const std::string&);\nVector2D                                configStringToVector2D(const std::string&);\nstd::optional<float>                    getPlusMinusKeywordResult(std::string in, float relative);\ndouble                                  normalizeAngleRad(double ang);\nstd::vector<SCallstackFrameInfo>        getBacktrace();\n[[noreturn]] void                       throwError(const std::string& err);\nHyprutils::OS::CFileDescriptor          allocateSHMFile(size_t len);\nbool                                    allocateSHMFilePair(size_t size, Hyprutils::OS::CFileDescriptor& rw_fd_ptr, Hyprutils::OS::CFileDescriptor& ro_fd_ptr);\nfloat                                   stringToPercentage(const std::string& VALUE, const float REL);\nbool                                    isNvidiaDriverVersionAtLeast(int threshold);\nstd::expected<std::string, std::string> binaryNameForWlClient(wl_client* client);\nstd::expected<std::string, std::string> binaryNameForPid(pid_t pid);\nstd::string                             deviceNameToInternalString(std::string in);\nstd::string                             getSystemLibraryVersion(const std::string& name);\nstd::string                             getBuiltSystemLibraryNames();\nbool                                    truthy(const std::string& str);\n\ntemplate <typename... Args>\n[[deprecated(\"use std::format instead\")]] std::string getFormat(std::format_string<Args...> fmt, Args&&... args) {\n    // no need for try {} catch {} because std::format_string<Args...> ensures that vformat never throw std::format_error\n    // because any suck format specifier will cause a compilation error\n    // this is actually what std::format in stdlib does\n    return std::vformat(fmt.get(), std::make_format_args(args...));\n}\n"
  },
  {
    "path": "src/helpers/Monitor.cpp",
    "content": "#include \"Monitor.hpp\"\n#include \"MiscFunctions.hpp\"\n#include \"../macros.hpp\"\n#include \"SharedDefs.hpp\"\n#include \"../helpers/TransferFunction.hpp\"\n#include \"math/Math.hpp\"\n#include \"../protocols/ColorManagement.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../protocols/GammaControl.hpp\"\n#include \"../devices/ITouch.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../protocols/PresentationTime.hpp\"\n#include \"../protocols/DRMLease.hpp\"\n#include \"../protocols/DRMSyncobj.hpp\"\n#include \"../protocols/core/Output.hpp\"\n#include \"../protocols/Screencopy.hpp\"\n#include \"../protocols/ToplevelExport.hpp\"\n#include \"../managers/PointerManager.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../protocols/core/DataDevice.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../managers/EventManager.hpp\"\n#include \"../managers/screenshare/ScreenshareManager.hpp\"\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../managers/animation/DesktopAnimationManager.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../hyprerror/HyprError.hpp\"\n#include \"../layout/LayoutManager.hpp\"\n#include \"../i18n/Engine.hpp\"\n#include \"../helpers/cm/ColorManagement.hpp\"\n#include \"sync/SyncTimeline.hpp\"\n#include \"time/Time.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"Drm.hpp\"\n#include <aquamarine/output/Output.hpp>\n#include \"debug/log/Logger.hpp\"\n#include \"debug/HyprNotificationOverlay.hpp\"\n#include \"MonitorFrameScheduler.hpp\"\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\n#include <cstring>\n#include <climits>\n#include <optional>\n#include <ranges>\n#include <vector>\n#include <algorithm>\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Utils;\nusing namespace Hyprutils::OS;\nusing enum NContentType::eContentType;\nusing namespace NColorManagement;\n\nCMonitor::CMonitor(SP<Aquamarine::IOutput> output_) : m_state(this), m_output(output_), m_imageDescription(DEFAULT_IMAGE_DESCRIPTION) {\n    g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig(\"specialWorkspaceIn\"), AVARDAMAGE_NONE);\n    m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });\n    static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>(\"cursor:zoom_factor\");\n    g_pAnimationManager->createAnimation(*PZOOMFACTOR, m_cursorZoom, g_pConfigManager->getAnimationPropertyConfig(\"zoomFactor\"), AVARDAMAGE_NONE);\n    m_cursorZoom->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });\n    g_pAnimationManager->createAnimation(0.F, m_zoomAnimProgress, g_pConfigManager->getAnimationPropertyConfig(\"monitorAdded\"), AVARDAMAGE_NONE);\n    m_zoomAnimProgress->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });\n    g_pAnimationManager->createAnimation(0.F, m_backgroundOpacity, g_pConfigManager->getAnimationPropertyConfig(\"monitorAdded\"), AVARDAMAGE_NONE);\n    m_backgroundOpacity->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });\n    g_pAnimationManager->createAnimation(0.F, m_dpmsBlackOpacity, g_pConfigManager->getAnimationPropertyConfig(\"fadeDpms\"), AVARDAMAGE_NONE);\n    m_dpmsBlackOpacity->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });\n}\n\nCMonitor::~CMonitor() {\n    m_events.destroy.emit();\n    if (g_pHyprRenderer && g_pHyprRenderer->glBackend())\n        g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self);\n}\n\nvoid CMonitor::onConnect(bool noRule) {\n    Event::bus()->m_events.monitor.preAdded.emit(m_self.lock());\n    CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }};\n\n    m_zoomAnimProgress->setValueAndWarp(0.F);\n    m_zoomAnimFrameCounter = 0;\n\n    g_pEventLoopManager->doLater([] {\n        g_pConfigManager->ensurePersistentWorkspacesPresent();\n        g_pCompositor->ensureWorkspacesOnAssignedMonitors();\n    });\n\n    m_listeners.frame      = m_output->events.frame.listen([this] {\n        if (m_frameScheduler)\n            m_frameScheduler->onFrame();\n    });\n    m_listeners.commit     = m_output->events.commit.listen([this] {\n        m_events.commit.emit();\n\n        // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER\n        if (true && Screenshare::mgr())\n            Screenshare::mgr()->onOutputCommit(m_self.lock());\n    });\n    m_listeners.needsFrame = m_output->events.needsFrame.listen([this] { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); });\n\n    m_listeners.presented = m_output->events.present.listen([this](const Aquamarine::IOutput::SPresentEvent& event) {\n        if (m_pendingDpmsAnimation) {\n            m_pendingDpmsAnimationCounter++;\n            // we give ourselves 5 frames of a buffer. The first presentation event still doesn't usually say that we actually\n            // are scanning out to the CRTC, and it could still be modesetting.\n            // this is not ideal (some CRTCs will just eat frames) but it's better than nothing\n\n            m_dpmsBlackOpacity->setValueAndWarp(1.F);\n\n            if (m_pendingDpmsAnimationCounter == 5) {\n                *m_dpmsBlackOpacity    = 0.F;\n                m_pendingDpmsAnimation = false;\n            }\n        }\n\n        timespec* ts = event.when;\n\n        if (ts && ts->tv_sec <= 2) {\n            // drop this timestamp, it's not valid. Likely drm is cringe. We can't push it further because\n            // a) it's wrong, b) our translations aren't 100% accurate and risk underflows\n            ts = nullptr;\n        }\n\n        if (!ts) {\n            timespec mono{};\n            clock_gettime(CLOCK_MONOTONIC, &mono);\n            PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK);\n        } else\n            PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags);\n\n        if (m_zoomAnimFrameCounter < 5) {\n            m_zoomAnimFrameCounter++;\n\n            // we give ourselves 5 frames of a buffer. The first presentation event still doesn't usually say that we actually\n            // are scanning out to the CRTC, and it could still be modesetting.\n            // this is not ideal (some CRTCs will just eat frames) but it's better than nothing\n            m_zoomAnimProgress->setValueAndWarp(0.F);\n            if (m_zoomAnimFrameCounter == 5) {\n                // start the animation for realzies\n                *m_zoomAnimProgress = 1.F;\n            }\n\n            // damage the entire display to force a frame immediately\n            g_pEventLoopManager->doLater([self = m_self] {\n                if (!self)\n                    return;\n\n                g_pHyprRenderer->damageMonitor(self.lock());\n            });\n        }\n\n        m_frameScheduler->onPresented();\n\n        m_events.presented.emit();\n    });\n\n    m_listeners.destroy = m_output->events.destroy.listen([this] {\n        Log::logger->log(Log::DEBUG, \"Destroy called for monitor {}\", m_name);\n\n        onDisconnect(true);\n\n        m_output              = nullptr;\n        m_renderingInitPassed = false;\n\n        Log::logger->log(Log::DEBUG, \"Removing monitor {} from realMonitors\", m_name);\n\n        std::erase_if(g_pCompositor->m_realMonitors, [&](PHLMONITOR& el) { return el.get() == this; });\n    });\n\n    m_listeners.state = m_output->events.state.listen([this](const Aquamarine::IOutput::SStateEvent& event) {\n        if (event.size == Vector2D{}) {\n            // an indication to re-set state\n            // we can't do much for createdByUser displays I think\n            if (m_createdByUser)\n                return;\n\n            Log::logger->log(Log::DEBUG, \"Reapplying monitor rule for {} from a state request\", m_name);\n            applyMonitorRule(&m_activeMonitorRule, true);\n            return;\n        }\n\n        if (!m_createdByUser)\n            return;\n\n        const auto SIZE = event.size;\n\n        m_forceSize = SIZE;\n\n        SMonitorRule rule = m_activeMonitorRule;\n\n        if (SIZE == rule.resolution)\n            return;\n\n        rule.resolution = SIZE;\n\n        applyMonitorRule(&rule);\n    });\n\n    m_frameScheduler         = makeUnique<CMonitorFrameScheduler>(m_self.lock());\n    m_frameScheduler->m_self = WP<CMonitorFrameScheduler>(m_frameScheduler);\n\n    m_tearingState.canTear = m_output->getBackend()->type() == Aquamarine::AQ_BACKEND_DRM;\n\n    m_name = m_output->name;\n\n    m_description = m_output->description;\n    // remove comma character from description. This allow monitor specific rules to work on monitor with comma on their description\n    std::erase(m_description, ',');\n\n    // field is backwards-compatible with intended usage of `szDescription` but excludes the parenthesized DRM node name suffix\n    m_shortDescription = trim(std::format(\"{} {} {}\", m_output->make, m_output->model, m_output->serial));\n    std::erase(m_shortDescription, ',');\n\n    if (m_output->getBackend()->type() != Aquamarine::AQ_BACKEND_DRM)\n        m_createdByUser = true; // should be true. WL and Headless backends should be addable / removable\n\n    // get monitor rule that matches\n    SMonitorRule monitorRule = g_pConfigManager->getMonitorRuleFor(m_self.lock());\n\n    if (m_enabled && !monitorRule.disabled) {\n        applyMonitorRule(&monitorRule, m_pixelSize == Vector2D{});\n\n        m_output->state->resetExplicitFences();\n        m_output->state->setEnabled(true);\n        m_state.commit();\n        return;\n    }\n\n    // if it's disabled, disable and ignore\n    if (monitorRule.disabled) {\n\n        m_output->state->resetExplicitFences();\n        m_output->state->setEnabled(false);\n\n        if (!m_state.commit())\n            Log::logger->log(Log::ERR, \"Couldn't commit disabled state on output {}\", m_output->name);\n\n        m_enabled = false;\n\n        m_listeners.frame.reset();\n        return;\n    }\n\n    if (m_output->nonDesktop) {\n        Log::logger->log(Log::DEBUG, \"Not configuring non-desktop output\");\n\n        for (auto& [name, lease] : PROTO::lease) {\n            if (!lease || m_output->getBackend() != lease->getBackend())\n                continue;\n\n            lease->offer(m_self.lock());\n        }\n\n        return;\n    }\n\n    PHLMONITOR* thisWrapper = nullptr;\n\n    // find the wrap\n    for (auto& m : g_pCompositor->m_realMonitors) {\n        if (m->m_id == m_id) {\n            thisWrapper = &m;\n            break;\n        }\n    }\n\n    RASSERT(thisWrapper->get(), \"CMonitor::onConnect: Had no wrapper???\");\n\n    if (std::ranges::find_if(g_pCompositor->m_monitors, [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_monitors.end())\n        g_pCompositor->m_monitors.push_back(*thisWrapper);\n\n    m_enabled = true;\n\n    m_output->state->resetExplicitFences();\n    m_output->state->setEnabled(true);\n\n    // set mode, also applies\n    if (!noRule)\n        applyMonitorRule(&monitorRule, true);\n\n    if (!m_state.commit())\n        Log::logger->log(Log::WARN, \"state.commit() failed in CMonitor::onCommit\");\n\n    m_damage.setSize(m_transformedSize);\n\n    Log::logger->log(Log::DEBUG, \"Added new monitor with name {} at {:j0} with size {:j0}, pointer {:x}\", m_output->name, m_position, m_pixelSize, rc<uintptr_t>(m_output.get()));\n\n    setupDefaultWS(monitorRule);\n\n    for (auto const& ws : g_pCompositor->getWorkspacesCopy()) {\n        if (!valid(ws))\n            continue;\n\n        const auto CURRENTMON = ws->m_monitor.lock();\n        const bool ORPHANED   = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; });\n        const bool RETURNING  = ws->m_lastMonitor == m_name;\n        const bool RECOVERY   = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces\n\n        if (RETURNING || RECOVERY) {\n            g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());\n            g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);\n            if (RETURNING)\n                ws->m_lastMonitor = \"\";\n        }\n    }\n\n    m_scale = monitorRule.scale;\n    if (m_scale < 0.1)\n        m_scale = getDefaultScale();\n\n    m_forceFullFrames = 3; // force 3 full frames to make sure there is no blinking due to double-buffering.\n    //\n\n    if (!m_activeMonitorRule.mirrorOf.empty())\n        setMirror(m_activeMonitorRule.mirrorOf);\n\n    if (!Desktop::focusState()->monitor()) // set the last monitor if it isn't set yet\n        Desktop::focusState()->rawMonitorFocus(m_self.lock());\n\n    g_pHyprRenderer->arrangeLayersForMonitor(m_id);\n    g_layoutManager->recalculateMonitor(m_self.lock());\n\n    // ensure VRR (will enable if necessary)\n    g_pConfigManager->ensureVRR(m_self.lock());\n\n    // verify last mon valid\n    bool found = false;\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m == Desktop::focusState()->monitor()) {\n            found = true;\n            break;\n        }\n    }\n\n    Log::logger->log(Log::DEBUG, \"checking if we have seen this monitor before: {}\", m_name);\n    // if we saw this monitor before, set it to the workspace it was on\n    if (g_pCompositor->m_seenMonitorWorkspaceMap.contains(m_name)) {\n        auto workspaceID = g_pCompositor->m_seenMonitorWorkspaceMap[m_name];\n        Log::logger->log(Log::DEBUG, \"Monitor {} was on workspace {}, setting it to that\", m_name, workspaceID);\n        auto ws = g_pCompositor->getWorkspaceByID(workspaceID);\n        if (ws) {\n            g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());\n            changeWorkspace(ws, true, false, false);\n        }\n    } else\n        Log::logger->log(Log::DEBUG, \"Monitor {} was not on any workspace\", m_name);\n\n    if (!found)\n        Desktop::focusState()->rawMonitorFocus(m_self.lock());\n\n    g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEW_MONITOR);\n\n    PROTO::gamma->applyGammaToState(m_self.lock());\n\n    m_events.connect.emit();\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"monitoradded\", m_name});\n    g_pEventManager->postEvent(SHyprIPCEvent{\"monitoraddedv2\", std::format(\"{},{},{}\", m_id, m_name, m_shortDescription)});\n    Event::bus()->m_events.monitor.added.emit(m_self.lock());\n}\n\nvoid CMonitor::onDisconnect(bool destroy) {\n    Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock());\n    CScopeGuard x = {[this]() {\n        if (g_pCompositor->m_isShuttingDown)\n            return;\n        g_pEventManager->postEvent(SHyprIPCEvent{\"monitorremoved\", m_name});\n        g_pEventManager->postEvent(SHyprIPCEvent{\"monitorremovedv2\", std::format(\"{},{},{}\", m_id, m_name, m_shortDescription)});\n        Event::bus()->m_events.monitor.removed.emit(m_self.lock());\n        g_pCompositor->scheduleMonitorStateRecheck();\n    }};\n\n    m_frameScheduler.reset();\n\n    if (!m_enabled || g_pCompositor->m_isShuttingDown)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"onDisconnect called for {}\", m_output->name);\n\n    m_events.disconnect.emit();\n    if (g_pHyprRenderer && g_pHyprRenderer->glBackend())\n        g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self);\n\n    // record what workspace this monitor was on\n    if (m_activeWorkspace) {\n        Log::logger->log(Log::DEBUG, \"Disconnecting Monitor {} was on workspace {}\", m_name, m_activeWorkspace->m_id);\n        g_pCompositor->m_seenMonitorWorkspaceMap[m_name] = m_activeWorkspace->m_id;\n    }\n\n    // Cleanup everything. Move windows back, snap cursor, shit.\n    PHLMONITOR BACKUPMON = nullptr;\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m.get() != this) {\n            BACKUPMON = m;\n            break;\n        }\n    }\n\n    // remove mirror\n    if (m_mirrorOf) {\n        m_mirrorOf->m_mirrors.erase(std::ranges::find_if(m_mirrorOf->m_mirrors, [&](const auto& other) { return other == m_self; }));\n\n        // unlock software for mirrored monitor\n        g_pPointerManager->unlockSoftwareForMonitor(m_mirrorOf.lock());\n        m_mirrorOf.reset();\n    }\n\n    if (!m_mirrors.empty()) {\n        for (auto const& m : m_mirrors) {\n            m->setMirror(\"\");\n        }\n\n        g_pConfigManager->m_wantsMonitorReload = true;\n    }\n\n    m_listeners.frame.reset();\n    m_listeners.presented.reset();\n    m_listeners.needsFrame.reset();\n    m_listeners.commit.reset();\n\n    for (size_t i = 0; i < 4; ++i) {\n        for (auto const& ls : m_layerSurfaceLayers[i]) {\n            if (ls->m_layerSurface && !ls->m_fadingOut)\n                ls->m_layerSurface->sendClosed();\n        }\n        m_layerSurfaceLayers[i].clear();\n    }\n\n    Log::logger->log(Log::DEBUG, \"Removed monitor {}!\", m_name);\n\n    if (!BACKUPMON) {\n        Log::logger->log(Log::WARN, \"Unplugged last monitor, entering an unsafe state. Good luck my friend.\");\n        g_pCompositor->enterUnsafeState();\n    }\n\n    m_enabled             = false;\n    m_renderingInitPassed = false;\n\n    std::vector<PHLWORKSPACE> wspToMove;\n    for (auto const& w : g_pCompositor->getWorkspaces()) {\n        if (w->m_monitor == m_self || !w->m_monitor)\n            wspToMove.emplace_back(w.lock());\n    }\n\n    // Preserve ownership across cascaded monitor disconnects.\n    // The first disconnected monitor \"owns\" where a workspace should return.\n    for (auto const& w : wspToMove) {\n        if (w && w->m_lastMonitor.empty())\n            w->m_lastMonitor = m_name;\n    }\n\n    if (BACKUPMON) {\n        // snap cursor\n        g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);\n\n        for (auto const& w : wspToMove) {\n            g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);\n            g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);\n        }\n    } else {\n        Desktop::focusState()->surface().reset();\n        Desktop::focusState()->window().reset();\n        Desktop::focusState()->monitor().reset();\n    }\n\n    if (m_activeWorkspace)\n        m_activeWorkspace->m_visible = false;\n    m_activeWorkspace.reset();\n\n    m_output->state->resetExplicitFences();\n    m_output->state->setAdaptiveSync(false);\n    m_output->state->setEnabled(false);\n\n    if (!m_state.commit())\n        Log::logger->log(Log::WARN, \"state.commit() failed in CMonitor::onDisconnect\");\n\n    if (Desktop::focusState()->monitor() == m_self)\n        Desktop::focusState()->rawMonitorFocus(BACKUPMON ? BACKUPMON : g_pCompositor->m_unsafeOutput.lock());\n\n    if (g_pHyprRenderer->m_mostHzMonitor == m_self) {\n        int        mostHz         = 0;\n        PHLMONITOR pMonitorMostHz = nullptr;\n\n        for (auto const& m : g_pCompositor->m_monitors) {\n            if (m->m_refreshRate > mostHz && m != m_self) {\n                pMonitorMostHz = m;\n                mostHz         = m->m_refreshRate;\n            }\n        }\n\n        g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz;\n    }\n\n    std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; });\n}\n\nstatic NColorManagement::eTransferFunction chooseTF(NTransferFunction::eTF tf) {\n    const auto sdrEOTF = NTransferFunction::fromConfig();\n\n    switch (tf) {\n        case NTransferFunction::TF_DEFAULT:\n        case NTransferFunction::TF_GAMMA22:\n        case NTransferFunction::TF_FORCED_GAMMA22: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;\n        case NTransferFunction::TF_SRGB: return NColorManagement::CM_TRANSFER_FUNCTION_SRGB;\n\n        case NTransferFunction::TF_AUTO: // use global setting\n            switch (sdrEOTF) {\n                case NTransferFunction::TF_AUTO: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;\n                default: return chooseTF(sdrEOTF);\n            }\n\n        default: UNREACHABLE();\n    }\n}\n\nvoid CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf) {\n    auto                                                              oldImageDescription = m_imageDescription;\n    const auto                                                        chosenSdrEotf       = chooseTF(cmSdrEotf);\n\n    const auto                                                        masteringPrimaries  = getMasteringPrimaries();\n    const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances();\n\n    const auto                                                        maxFALL = this->maxFALL();\n    const auto                                                        maxCLL  = this->maxCLL();\n\n    switch (cmType) {\n        case NCMType::CM_SRGB:\n            m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,\n                                                          .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,\n                                                          .primaries        = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB)});\n            break; // assumes SImageDescription defaults to sRGB\n        case NCMType::CM_WIDE:\n            m_imageDescription = CImageDescription::from({.transferFunction    = chosenSdrEotf,\n                                                          .primariesNameSet    = true,\n                                                          .primariesNamed      = NColorManagement::CM_PRIMARIES_BT2020,\n                                                          .primaries           = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),\n                                                          .masteringPrimaries  = masteringPrimaries,\n                                                          .masteringLuminances = masteringLuminances,\n                                                          .maxCLL              = maxCLL,\n                                                          .maxFALL             = maxFALL});\n            break;\n        case NCMType::CM_DCIP3:\n            m_imageDescription = CImageDescription::from({.transferFunction    = chosenSdrEotf,\n                                                          .primariesNameSet    = true,\n                                                          .primariesNamed      = NColorManagement::CM_PRIMARIES_DCI_P3,\n                                                          .primaries           = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3),\n                                                          .masteringPrimaries  = masteringPrimaries,\n                                                          .masteringLuminances = masteringLuminances,\n                                                          .maxCLL              = maxCLL,\n                                                          .maxFALL             = maxFALL});\n            break;\n        case NCMType::CM_DP3:\n            m_imageDescription = CImageDescription::from({.transferFunction    = chosenSdrEotf,\n                                                          .primariesNameSet    = true,\n                                                          .primariesNamed      = NColorManagement::CM_PRIMARIES_DISPLAY_P3,\n                                                          .primaries           = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3),\n                                                          .masteringPrimaries  = masteringPrimaries,\n                                                          .masteringLuminances = masteringLuminances,\n                                                          .maxCLL              = maxCLL,\n                                                          .maxFALL             = maxFALL});\n            break;\n        case NCMType::CM_ADOBE:\n            m_imageDescription = CImageDescription::from({.transferFunction    = chosenSdrEotf,\n                                                          .primariesNameSet    = true,\n                                                          .primariesNamed      = NColorManagement::CM_PRIMARIES_ADOBE_RGB,\n                                                          .primaries           = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB),\n                                                          .masteringPrimaries  = masteringPrimaries,\n                                                          .masteringLuminances = masteringLuminances,\n                                                          .maxCLL              = maxCLL,\n                                                          .maxFALL             = maxFALL});\n            break;\n        case NCMType::CM_EDID:\n            m_imageDescription = CImageDescription::from({.transferFunction    = chosenSdrEotf,\n                                                          .primariesNameSet    = false,\n                                                          .primariesNamed      = NColorManagement::CM_PRIMARIES_BT2020,\n                                                          .primaries           = masteringPrimaries,\n                                                          .masteringPrimaries  = masteringPrimaries,\n                                                          .masteringLuminances = masteringLuminances,\n                                                          .maxCLL              = maxCLL,\n                                                          .maxFALL             = maxFALL});\n            break;\n        case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break;\n        case NCMType::CM_HDR_EDID:\n            m_imageDescription = CImageDescription::from(\n                {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,\n                 .primariesNameSet = false,\n                 .primariesNamed   = NColorManagement::CM_PRIMARIES_BT2020,\n                 .primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? masteringPrimaries : NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),\n                 .masteringPrimaries  = masteringPrimaries,\n                 .luminances          = {.min       = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMinLuminance(),\n                                         .max       = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMaxLuminance(),\n                                         .reference = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFRefLuminance()},\n                 .masteringLuminances = masteringLuminances,\n                 .maxCLL              = maxCLL,\n                 .maxFALL             = maxFALL});\n\n            break;\n        default: UNREACHABLE();\n    }\n    if ((m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) && (cmType == NCMType::CM_HDR || cmType == NCMType::CM_HDR_EDID))\n        m_imageDescription = m_imageDescription->with({\n            .min       = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, //\n            .max       = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, //\n            .reference = m_imageDescription->value().luminances.reference                                   //\n        });\n\n    if (oldImageDescription != m_imageDescription) {\n        if (PROTO::colorManagement)\n            PROTO::colorManagement->onMonitorImageDescriptionChanged(m_self);\n    }\n}\n\nbool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {\n\n    static auto PDISABLESCALECHECKS = CConfigValue<Hyprlang::INT>(\"debug:disable_scale_checks\");\n\n    Log::logger->log(Log::DEBUG, \"Applying monitor rule for {}\", m_name);\n\n    m_activeMonitorRule = *pMonitorRule;\n\n    if (m_forceSize.has_value())\n        m_activeMonitorRule.resolution = m_forceSize.value();\n\n    const auto RULE = &m_activeMonitorRule;\n\n    // if it's disabled, disable and ignore\n    if (RULE->disabled) {\n        if (m_enabled)\n            onDisconnect();\n\n        m_events.modeChanged.emit();\n\n        return true;\n    }\n\n    // don't touch VR headsets\n    if (m_output->nonDesktop)\n        return true;\n\n    if (!m_enabled) {\n        onConnect(true); // enable it.\n        Log::logger->log(Log::DEBUG, \"Monitor {} is disabled but is requested to be enabled\", m_name);\n        force = true;\n    }\n\n    // Check if the rule isn't already applied\n    // TODO: clean this up lol\n    if (!force && DELTALESSTHAN(m_pixelSize.x, RULE->resolution.x, 1) /* ↓ */\n        && DELTALESSTHAN(m_pixelSize.y, RULE->resolution.y, 1)        /* Resolution is the same */\n        && m_pixelSize.x > 1 && m_pixelSize.y > 1                     /* Active resolution is not invalid */\n        && DELTALESSTHAN(m_refreshRate, RULE->refreshRate, 1)         /* Refresh rate is the same */\n        && m_setScale == RULE->scale                                  /* Scale is the same */\n        && m_autoDir == RULE->autoDir                                 /* Auto direction is the same */\n        /* position is set correctly */\n        && ((DELTALESSTHAN(m_position.x, RULE->offset.x, 1) && DELTALESSTHAN(m_position.y, RULE->offset.y, 1)) || RULE->offset == Vector2D(-INT32_MAX, -INT32_MAX))\n        /* other properties hadn't changed */\n        && m_transform == RULE->transform && RULE->enable10bit == m_enabled10bit && RULE->cmType == m_cmType && RULE->sdrSaturation == m_sdrSaturation &&\n        RULE->sdrBrightness == m_sdrBrightness && RULE->sdrMinLuminance == m_minLuminance && RULE->sdrMaxLuminance == m_maxLuminance &&\n        RULE->supportsWideColor == m_supportsWideColor && RULE->supportsHDR == m_supportsHDR && RULE->minLuminance == m_minLuminance && RULE->maxLuminance == m_maxLuminance &&\n        RULE->maxAvgLuminance == m_maxAvgLuminance && !std::memcmp(&m_customDrmMode, &RULE->drmMode, sizeof(m_customDrmMode)) && m_reservedArea == RULE->reservedArea) {\n\n        Log::logger->log(Log::DEBUG, \"Not applying a new rule to {} because it's already applied!\", m_name);\n\n        setMirror(RULE->mirrorOf);\n\n        return true;\n    }\n\n    bool autoScale = false;\n\n    if (RULE->scale > 0.1)\n        m_scale = RULE->scale;\n    else {\n        autoScale               = true;\n        const auto DEFAULTSCALE = getDefaultScale();\n        m_scale                 = DEFAULTSCALE;\n    }\n\n    m_setScale     = m_scale;\n    m_transform    = RULE->transform;\n    m_autoDir      = RULE->autoDir;\n    m_reservedArea = RULE->reservedArea;\n\n    // accumulate requested modes in reverse order (cause inesrting at front is inefficient)\n    std::vector<SP<Aquamarine::SOutputMode>> requestedModes;\n    std::string                              requestedStr = \"unknown\";\n\n    // use sortFunc, add best 3 to requestedModes in reverse, since we test in reverse\n    auto addBest3Modes = [&](auto const& sortFunc) {\n        auto sortedModes = m_output->modes;\n        std::ranges::sort(sortedModes, sortFunc);\n        if (sortedModes.size() > 3)\n            sortedModes.erase(sortedModes.begin() + 3, sortedModes.end());\n        requestedModes.insert_range(requestedModes.end(), sortedModes | std::views::reverse);\n    };\n\n    // last fallback is always preferred mode\n    if (!m_output->preferredMode())\n        Log::logger->log(Log::ERR, \"Monitor {} has NO PREFERRED MODE\", m_output->name);\n    else\n        requestedModes.push_back(m_output->preferredMode());\n\n    if (RULE->resolution == Vector2D()) {\n        requestedStr = \"preferred\";\n\n        // fallback to first 3 modes if preferred fails/doesn't exist\n        requestedModes = m_output->modes;\n        if (requestedModes.size() > 3)\n            requestedModes.erase(requestedModes.begin() + 3, requestedModes.end());\n        std::ranges::reverse(requestedModes.begin(), requestedModes.end());\n\n        if (m_output->preferredMode())\n            requestedModes.push_back(m_output->preferredMode());\n    } else if (RULE->resolution == Vector2D(-1, -1)) {\n        requestedStr = \"highrr\";\n\n        // sort prioritizing refresh rate 1st and resolution 2nd, then add best 3\n        addBest3Modes([](auto const& a, auto const& b) {\n            if (std::round(a->refreshRate) > std::round(b->refreshRate))\n                return true;\n            else if (DELTALESSTHAN(sc<float>(a->refreshRate), sc<float>(b->refreshRate), 1.F) && a->pixelSize.x > b->pixelSize.x && a->pixelSize.y > b->pixelSize.y)\n                return true;\n            return false;\n        });\n    } else if (RULE->resolution == Vector2D(-1, -2)) {\n        requestedStr = \"highres\";\n\n        // sort prioritizing resolution 1st and refresh rate 2nd, then add best 3\n        addBest3Modes([](auto const& a, auto const& b) {\n            if (a->pixelSize.x > b->pixelSize.x && a->pixelSize.y > b->pixelSize.y)\n                return true;\n            else if (DELTALESSTHAN(a->pixelSize.x, b->pixelSize.x, 1) && DELTALESSTHAN(a->pixelSize.y, b->pixelSize.y, 1) &&\n                     std::round(a->refreshRate) > std::round(b->refreshRate))\n                return true;\n            return false;\n        });\n    } else if (RULE->resolution == Vector2D(-1, -3)) {\n        requestedStr = \"maxwidth\";\n\n        // sort prioritizing widest resolution 1st and refresh rate 2nd, then add best 3\n        addBest3Modes([](auto const& a, auto const& b) {\n            if (a->pixelSize.x > b->pixelSize.x)\n                return true;\n            if (a->pixelSize.x == b->pixelSize.x && std::round(a->refreshRate) > std::round(b->refreshRate))\n                return true;\n            return false;\n        });\n    } else if (RULE->resolution != Vector2D()) {\n        // user requested mode\n        requestedStr = std::format(\"{:X0}@{:.2f}Hz\", RULE->resolution, RULE->refreshRate);\n\n        // sort by closeness to requested, then add best 3\n        addBest3Modes([&](auto const& a, auto const& b) {\n            if (abs(a->pixelSize.x - RULE->resolution.x) < abs(b->pixelSize.x - RULE->resolution.x))\n                return true;\n            if (a->pixelSize.x == b->pixelSize.x && abs(a->pixelSize.y - RULE->resolution.y) < abs(b->pixelSize.y - RULE->resolution.y))\n                return true;\n            if (a->pixelSize == b->pixelSize && abs((a->refreshRate / 1000.f) - RULE->refreshRate) < abs((b->refreshRate / 1000.f) - RULE->refreshRate))\n                return true;\n            return false;\n        });\n\n        // if the best mode isn't close to requested, then try requested as custom mode first\n        if (!requestedModes.empty()) {\n            auto bestMode = requestedModes.back();\n            if (!DELTALESSTHAN(bestMode->pixelSize.x, RULE->resolution.x, 1) || !DELTALESSTHAN(bestMode->pixelSize.y, RULE->resolution.y, 1) ||\n                !DELTALESSTHAN(bestMode->refreshRate / 1000.f, RULE->refreshRate, 1))\n                requestedModes.push_back(makeShared<Aquamarine::SOutputMode>(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = RULE->refreshRate * 1000.f}));\n        }\n\n        // then if requested is custom, try custom mode first\n        if (RULE->drmMode.type == DRM_MODE_TYPE_USERDEF) {\n            if (m_output->getBackend()->type() != Aquamarine::eBackendType::AQ_BACKEND_DRM)\n                Log::logger->log(Log::ERR, \"Tried to set custom modeline on non-DRM output\");\n            else\n                requestedModes.push_back(makeShared<Aquamarine::SOutputMode>(\n                    Aquamarine::SOutputMode{.pixelSize = {RULE->drmMode.hdisplay, RULE->drmMode.vdisplay}, .refreshRate = RULE->drmMode.vrefresh, .modeInfo = RULE->drmMode}));\n        }\n    }\n\n    const auto WAS10B  = m_enabled10bit;\n    const auto OLDRES  = m_pixelSize;\n    bool       success = false;\n\n    // Needed in case we are switching from a custom modeline to a standard mode\n    m_customDrmMode = {};\n    m_currentMode   = nullptr;\n\n    m_output->state->setFormat(DRM_FORMAT_XRGB8888);\n    m_prevDrmFormat = m_drmFormat;\n    m_drmFormat     = DRM_FORMAT_XRGB8888;\n    m_output->state->resetExplicitFences();\n\n    if (Env::isTrace()) {\n        Log::logger->log(Log::TRACE, \"Monitor {} requested modes:\", m_name);\n        if (requestedModes.empty())\n            Log::logger->log(Log::TRACE, \"| None\");\n        else {\n            for (auto const& mode : requestedModes | std::views::reverse) {\n                Log::logger->log(Log::TRACE, \"| {:X0}@{:.2f}Hz\", mode->pixelSize, mode->refreshRate / 1000.f);\n            }\n        }\n    }\n\n    for (auto const& mode : requestedModes | std::views::reverse) {\n        std::string modeStr = std::format(\"{:X0}@{:.2f}Hz\", mode->pixelSize, mode->refreshRate / 1000.f);\n\n        if (mode->modeInfo.has_value() && mode->modeInfo->type == DRM_MODE_TYPE_USERDEF) {\n            m_output->state->setCustomMode(mode);\n\n            if (!m_state.test()) {\n                Log::logger->log(Log::ERR, \"Monitor {}: REJECTED custom mode {}!\", m_name, modeStr);\n                continue;\n            }\n\n            m_customDrmMode = mode->modeInfo.value();\n        } else {\n            m_output->state->setMode(mode);\n\n            if (!m_state.test()) {\n                Log::logger->log(Log::ERR, \"Monitor {}: REJECTED available mode {}!\", m_name, modeStr);\n                if (mode->preferred)\n                    Log::logger->log(Log::ERR, \"Monitor {}: REJECTED preferred mode!!!\", m_name);\n                continue;\n            }\n\n            m_customDrmMode = {};\n        }\n\n        m_refreshRate = mode->refreshRate / 1000.f;\n        m_size        = mode->pixelSize;\n        m_currentMode = mode;\n\n        success = true;\n\n        if (mode->preferred)\n            Log::logger->log(Log::DEBUG, \"Monitor {}: requested {}, using preferred mode {}\", m_name, requestedStr, modeStr);\n        else if (mode->modeInfo.has_value() && mode->modeInfo->type == DRM_MODE_TYPE_USERDEF)\n            Log::logger->log(Log::DEBUG, \"Monitor {}: requested {}, using custom mode {}\", m_name, requestedStr, modeStr);\n        else\n            Log::logger->log(Log::DEBUG, \"Monitor {}: requested {}, using available mode {}\", m_name, requestedStr, modeStr);\n\n        break;\n    }\n\n    // try requested as custom mode jic it works\n    if (!success && RULE->resolution != Vector2D() && RULE->resolution != Vector2D(-1, -1) && RULE->resolution != Vector2D(-1, -2)) {\n        auto        refreshRate = m_output->getBackend()->type() == Aquamarine::eBackendType::AQ_BACKEND_DRM ? RULE->refreshRate * 1000 : 0;\n        auto        mode        = makeShared<Aquamarine::SOutputMode>(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = refreshRate});\n        std::string modeStr     = std::format(\"{:X0}@{:.2f}Hz\", mode->pixelSize, mode->refreshRate / 1000.f);\n\n        m_output->state->setCustomMode(mode);\n\n        if (m_state.test()) {\n            Log::logger->log(Log::DEBUG, \"Monitor {}: requested {}, using custom mode {}\", m_name, requestedStr, modeStr);\n\n            refreshRate     = mode->refreshRate / 1000.f;\n            m_size          = mode->pixelSize;\n            m_currentMode   = mode;\n            m_customDrmMode = {};\n\n            success = true;\n        } else\n            Log::logger->log(Log::ERR, \"Monitor {}: REJECTED custom mode {}!\", m_name, modeStr);\n    }\n\n    // try any of the modes if none of the above work\n    if (!success) {\n        for (auto const& mode : m_output->modes) {\n            m_output->state->setMode(mode);\n\n            if (!m_state.test())\n                continue;\n\n            auto errorMessage = I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_MODE_FAIL,\n                                                             {{\"name\", m_name}, {\"mode\", std::format(\"{:X0}@{:.2f}Hz\", mode->pixelSize, mode->refreshRate / 1000.f)}});\n            Log::logger->log(Log::WARN, errorMessage);\n            g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING);\n\n            m_refreshRate   = mode->refreshRate / 1000.f;\n            m_size          = mode->pixelSize;\n            m_currentMode   = mode;\n            m_customDrmMode = {};\n\n            success = true;\n\n            break;\n        }\n    }\n\n    if (!success) {\n        Log::logger->log(Log::ERR, \"Monitor {} has NO FALLBACK MODES, and an INVALID one was requested: {:X0}@{:.2f}Hz\", m_name, RULE->resolution, RULE->refreshRate);\n        return true;\n    }\n\n    m_vrrActive = m_output->state->state().adaptiveSync // disabled here, will be tested in CConfigManager::ensureVRR()\n        || m_createdByUser;                             // wayland backend doesn't allow for disabling adaptive_sync\n\n    m_pixelSize = m_size;\n\n    // clang-format off\n    static const std::array<std::vector<std::pair<std::string, uint32_t>>, 2> formats{\n        std::vector<std::pair<std::string, uint32_t>>{ /* 10-bit */\n            {\"DRM_FORMAT_XRGB2101010\", DRM_FORMAT_XRGB2101010}, {\"DRM_FORMAT_XBGR2101010\", DRM_FORMAT_XBGR2101010}, {\"DRM_FORMAT_XRGB8888\", DRM_FORMAT_XRGB8888}, {\"DRM_FORMAT_XBGR8888\", DRM_FORMAT_XBGR8888}\n        },\n        std::vector<std::pair<std::string, uint32_t>>{ /* 8-bit */\n            {\"DRM_FORMAT_XRGB8888\", DRM_FORMAT_XRGB8888}, {\"DRM_FORMAT_XBGR8888\", DRM_FORMAT_XBGR8888}\n        }\n    };\n    // clang-format on\n\n    bool set10bit = false;\n\n    for (auto const& fmt : formats[sc<int>(!RULE->enable10bit)]) {\n        m_output->state->setFormat(fmt.second);\n        m_prevDrmFormat = m_drmFormat;\n        m_drmFormat     = fmt.second;\n\n        if (!m_state.test()) {\n            Log::logger->log(Log::ERR, \"output {} failed basic test on format {}\", m_name, fmt.first);\n        } else {\n            Log::logger->log(Log::DEBUG, \"output {} succeeded basic test on format {}\", m_name, fmt.first);\n            if (RULE->enable10bit && fmt.first.contains(\"101010\"))\n                set10bit = true;\n            break;\n        }\n    }\n\n    m_enabled10bit = set10bit;\n\n    m_supportsWideColor = RULE->supportsHDR;\n    m_supportsHDR       = RULE->supportsHDR;\n\n    if (RULE->iccFile.empty()) {\n        // only apply explicit cm settings if we have no icc file\n\n        m_cmType = RULE->cmType;\n        switch (m_cmType) {\n            case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;\n            case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;\n            case NCMType::CM_HDR:\n            case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;\n            default: break;\n        }\n\n        m_sdrEotf = RULE->sdrEotf;\n\n        m_sdrMinLuminance = RULE->sdrMinLuminance;\n        m_sdrMaxLuminance = RULE->sdrMaxLuminance;\n\n        m_minLuminance    = RULE->minLuminance;\n        m_maxLuminance    = RULE->maxLuminance;\n        m_maxAvgLuminance = RULE->maxAvgLuminance;\n\n        applyCMType(m_cmType, m_sdrEotf);\n\n        m_sdrSaturation = RULE->sdrSaturation;\n        m_sdrBrightness = RULE->sdrBrightness;\n    } else {\n        auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile);\n        if (!image) {\n            Log::logger->log(Log::ERR, \"icc for {} ({}) failed: {}\", m_name, RULE->iccFile, image.error());\n            g_pConfigManager->addParseError(std::format(\"failed to apply icc {} to {}: {}\", RULE->iccFile, m_name, image.error()));\n        } else {\n            m_imageDescription = CImageDescription::from(*image);\n            if (!m_imageDescription) {\n                Log::logger->log(Log::ERR, \"icc for {} ({}) failed 2: {}\", m_name, RULE->iccFile, image.error());\n                g_pConfigManager->addParseError(std::format(\"failed to apply icc {} to {}: {}\", RULE->iccFile, m_name, image.error()));\n                m_imageDescription = CImageDescription::from(SImageDescription{});\n            }\n        }\n    }\n\n    Vector2D logicalSize = m_pixelSize / m_scale;\n    if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {\n        // invalid scale, will produce fractional pixels.\n        // find the nearest valid.\n\n        float    searchScale = std::round(m_scale * 120.0);\n        bool     found       = false;\n\n        double   scaleZero = searchScale / 120.0;\n\n        Vector2D logicalZero = m_pixelSize / scaleZero;\n        if (logicalZero == logicalZero.round())\n            m_scale = scaleZero;\n        else {\n            for (size_t i = 1; i < 90; ++i) {\n                double   scaleUp   = (searchScale + i) / 120.0;\n                double   scaleDown = (searchScale - i) / 120.0;\n\n                Vector2D logicalUp   = m_pixelSize / scaleUp;\n                Vector2D logicalDown = m_pixelSize / scaleDown;\n\n                if (logicalUp == logicalUp.round()) {\n                    found       = true;\n                    searchScale = scaleUp;\n                    break;\n                }\n                if (logicalDown == logicalDown.round()) {\n                    found       = true;\n                    searchScale = scaleDown;\n                    break;\n                }\n            }\n\n            if (!found) {\n                if (autoScale)\n                    m_scale = std::round(scaleZero);\n                else {\n                    Log::logger->log(Log::ERR, \"Invalid scale passed to monitor, {} failed to find a clean divisor\", m_scale);\n                    g_pConfigManager->addParseError(\"Invalid scale passed to monitor \" + m_name + \", failed to find a clean divisor\");\n                    m_scale = getDefaultScale();\n                }\n            } else {\n                if (!autoScale) {\n                    Log::logger->log(Log::ERR, \"Invalid scale passed to monitor, {} found suggestion {}\", m_scale, searchScale);\n                    static auto PDISABLENOTIFICATION = CConfigValue<Hyprlang::INT>(\"misc:disable_scale_notification\");\n                    if (!*PDISABLENOTIFICATION)\n                        g_pHyprNotificationOverlay->addNotification(\n                            I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                                                         {{\"name\", m_name}, {\"scale\", std::format(\"{:.2f}\", m_scale)}, {\"fixed_scale\", std::format(\"{:.2f}\", searchScale)}}),\n                            CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING);\n                }\n                m_scale = searchScale;\n            }\n        }\n    }\n\n    m_output->scheduleFrame();\n\n    if (!m_state.commit())\n        Log::logger->log(Log::ERR, \"Couldn't commit output named {}\", m_output->name);\n\n    Vector2D xfmd     = m_transform % 2 == 1 ? Vector2D{m_pixelSize.y, m_pixelSize.x} : m_pixelSize;\n    m_size            = (xfmd / m_scale).round();\n    m_transformedSize = xfmd;\n\n    if (m_createdByUser) {\n        CBox transformedBox = {0, 0, m_transformedSize.x, m_transformedSize.y};\n        transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_transform)), m_transformedSize.x, m_transformedSize.y);\n\n        m_pixelSize = Vector2D(transformedBox.width, transformedBox.height);\n    }\n\n    updateMatrix();\n\n    if ((WAS10B != m_enabled10bit || OLDRES != m_pixelSize)) {\n        m_mirrorFB.reset();\n        m_offloadFB.reset();\n        m_mirrorSwapFB.reset();\n        m_blurFB.reset();\n        m_offMainFB.reset();\n        m_stencilTex.reset();\n\n        if (g_pHyprRenderer && g_pHyprRenderer->glBackend())\n            g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self);\n    }\n\n    g_pCompositor->scheduleMonitorStateRecheck();\n\n    m_damage.setSize(m_transformedSize);\n\n    updateVCGTRamps();\n\n    // Set scale for all surfaces on this monitor, needed for some clients\n    // but not on unsafe state to avoid crashes\n    if (!g_pCompositor->m_unsafeState) {\n        for (auto const& w : g_pCompositor->m_windows) {\n            w->updateSurfaceScaleTransformDetails();\n        }\n    }\n    // updato us\n    g_pHyprRenderer->arrangeLayersForMonitor(m_id);\n\n    // reload to fix mirrors\n    g_pConfigManager->m_wantsMonitorReload = true;\n\n    Log::logger->log(Log::DEBUG, \"Monitor {} data dump: res {:X}@{:.2f}Hz, scale {:.2f}, transform {}, pos {:X}, 10b {}\", m_name, m_pixelSize, m_refreshRate, m_scale,\n                     sc<int>(m_transform), m_position, sc<int>(m_enabled10bit));\n\n    Event::bus()->m_events.monitor.layoutChanged.emit();\n\n    m_events.modeChanged.emit();\n\n    return true;\n}\n\nvoid CMonitor::addDamage(const pixman_region32_t* rg) {\n    if (m_cursorZoom->value() != 1.f && g_pCompositor->getMonitorFromCursor() == m_self) {\n        m_damage.damageEntire();\n        g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);\n    } else if (m_damage.damage(rg))\n        g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);\n}\n\nvoid CMonitor::addDamage(const CRegion& rg) {\n    addDamage(const_cast<CRegion*>(&rg)->pixman());\n}\n\nvoid CMonitor::addDamage(const CBox& box) {\n    if (m_cursorZoom->value() != 1.f && g_pCompositor->getMonitorFromCursor() == m_self) {\n        m_damage.damageEntire();\n        g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);\n    }\n\n    if (m_damage.damage(box))\n        g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);\n}\n\nbool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {\n    static auto PNOBREAK = CConfigValue<Hyprlang::INT>(\"cursor:no_break_fs_vrr\");\n    static auto PMINRR   = CConfigValue<Hyprlang::INT>(\"cursor:min_refresh_rate\");\n\n    // skip scheduling extra frames for fullsreen apps with vrr\n    const auto FS_WINDOW          = getFullscreenWindow();\n    const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor();\n    const bool noBreak            = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME));\n    const bool shouldSkip         = (!shouldRenderCursor || noBreak) && m_output->state->state().adaptiveSync;\n\n    // keep requested minimum refresh rate\n    if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) {\n        // damage whole screen because some previous cursor box damages were skipped\n        m_damage.damageEntire();\n        return false;\n    }\n\n    return shouldSkip;\n}\n\nbool CMonitor::isMirror() {\n    return m_mirrorOf != nullptr;\n}\n\nbool CMonitor::matchesStaticSelector(const std::string& selector) const {\n    if (selector.starts_with(\"desc:\")) {\n        // match by description\n        const auto DESCRIPTIONSELECTOR = trim(selector.substr(5));\n\n        return m_description.starts_with(DESCRIPTIONSELECTOR) || m_shortDescription.starts_with(DESCRIPTIONSELECTOR);\n    } else {\n        // match by selector\n        return m_name == selector;\n    }\n}\n\nWORKSPACEID CMonitor::findAvailableDefaultWS() {\n    for (WORKSPACEID i = 1; i < LONG_MAX; ++i) {\n        if (g_pCompositor->getWorkspaceByID(i))\n            continue;\n\n        if (const auto BOUND = g_pConfigManager->getBoundMonitorStringForWS(std::to_string(i)); !BOUND.empty() && BOUND != m_name)\n            continue;\n\n        return i;\n    }\n\n    return LONG_MAX; // shouldn't be reachable\n}\n\nvoid CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) {\n    // Workspace\n    std::string newDefaultWorkspaceName = \"\";\n    int64_t     wsID                    = WORKSPACE_INVALID;\n    if (g_pConfigManager->getDefaultWorkspaceFor(m_name).empty())\n        wsID = findAvailableDefaultWS();\n    else {\n        const auto ws           = getWorkspaceIDNameFromString(g_pConfigManager->getDefaultWorkspaceFor(m_name));\n        wsID                    = ws.id;\n        newDefaultWorkspaceName = ws.name;\n    }\n\n    if (wsID == WORKSPACE_INVALID || (wsID >= SPECIAL_WORKSPACE_START && wsID <= -2)) {\n        wsID                    = std::ranges::distance(g_pCompositor->getWorkspaces()) + 1;\n        newDefaultWorkspaceName = std::to_string(wsID);\n\n        Log::logger->log(Log::DEBUG, \"Invalid workspace= directive name in monitor parsing, workspace name \\\"{}\\\" is invalid.\", g_pConfigManager->getDefaultWorkspaceFor(m_name));\n    }\n\n    auto PNEWWORKSPACE = g_pCompositor->getWorkspaceByID(wsID);\n\n    Log::logger->log(Log::DEBUG, \"New monitor: WORKSPACEID {}, exists: {}\", wsID, sc<int>(PNEWWORKSPACE != nullptr));\n\n    if (PNEWWORKSPACE) {\n        // workspace exists, move it to the newly connected monitor\n        g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock());\n        m_activeWorkspace = PNEWWORKSPACE;\n        g_layoutManager->recalculateMonitor(m_self.lock());\n        g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);\n    } else {\n        if (newDefaultWorkspaceName.empty())\n            newDefaultWorkspaceName = std::to_string(wsID);\n\n        PNEWWORKSPACE = CWorkspace::create(wsID, m_self.lock(), newDefaultWorkspaceName);\n    }\n\n    m_activeWorkspace = PNEWWORKSPACE;\n\n    PNEWWORKSPACE->m_events.activeChanged.emit();\n    PNEWWORKSPACE->m_visible     = true;\n    PNEWWORKSPACE->m_lastMonitor = \"\";\n}\n\nvoid CMonitor::setMirror(const std::string& mirrorOf) {\n    const auto PMIRRORMON = g_pCompositor->getMonitorFromString(mirrorOf);\n\n    if (PMIRRORMON == m_mirrorOf)\n        return;\n\n    if (PMIRRORMON && PMIRRORMON->isMirror()) {\n        Log::logger->log(Log::ERR, \"Cannot mirror a mirror!\");\n        return;\n    }\n\n    if (PMIRRORMON == m_self) {\n        Log::logger->log(Log::ERR, \"Cannot mirror self!\");\n        return;\n    }\n\n    if (!PMIRRORMON) {\n        // disable mirroring\n\n        if (m_mirrorOf) {\n            m_mirrorOf->m_mirrors.erase(std::ranges::find_if(m_mirrorOf->m_mirrors, [&](const auto& other) { return other == m_self; }));\n\n            // unlock software for mirrored monitor\n            g_pPointerManager->unlockSoftwareForMonitor(m_mirrorOf.lock());\n        }\n\n        m_mirrorOf.reset();\n\n        // set rule\n        const auto RULE = g_pConfigManager->getMonitorRuleFor(m_self.lock());\n\n        m_position = RULE.offset;\n\n        // push to mvmonitors\n\n        PHLMONITOR* thisWrapper = nullptr;\n\n        // find the wrap\n        for (auto& m : g_pCompositor->m_realMonitors) {\n            if (m->m_id == m_id) {\n                thisWrapper = &m;\n                break;\n            }\n        }\n\n        RASSERT(thisWrapper->get(), \"CMonitor::setMirror: Had no wrapper???\");\n\n        if (std::ranges::find_if(g_pCompositor->m_monitors, [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_monitors.end()) {\n            g_pCompositor->m_monitors.push_back(*thisWrapper);\n        }\n\n        setupDefaultWS(RULE);\n\n        applyMonitorRule(const_cast<SMonitorRule*>(&RULE), true); // will apply the offset and stuff\n    } else {\n        PHLMONITOR BACKUPMON = nullptr;\n        for (auto const& m : g_pCompositor->m_monitors) {\n            if (m.get() != this) {\n                BACKUPMON = m;\n                break;\n            }\n        }\n\n        // move all the WS\n        std::vector<PHLWORKSPACE> wspToMove;\n        for (auto const& w : g_pCompositor->getWorkspaces()) {\n            if (w->m_monitor == m_self || !w->m_monitor)\n                wspToMove.emplace_back(w.lock());\n        }\n\n        for (auto const& w : wspToMove) {\n            g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);\n            g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);\n        }\n\n        m_activeWorkspace.reset();\n\n        m_position = PMIRRORMON->m_position;\n\n        m_mirrorOf = PMIRRORMON;\n\n        m_mirrorOf->m_mirrors.push_back(m_self);\n\n        // remove from mvmonitors\n        std::erase_if(g_pCompositor->m_monitors, [&](const auto& other) { return other == m_self; });\n\n        g_pCompositor->scheduleMonitorStateRecheck();\n\n        Desktop::focusState()->rawMonitorFocus(g_pCompositor->m_monitors.front());\n\n        // Software lock mirrored monitor\n        g_pPointerManager->lockSoftwareForMonitor(PMIRRORMON);\n    }\n\n    m_events.modeChanged.emit();\n}\n\nfloat CMonitor::getDefaultScale() {\n    if (!m_enabled)\n        return 1;\n\n    static constexpr double MMPERINCH = 25.4;\n\n    const auto              DIAGONALPX = sqrt(pow(m_pixelSize.x, 2) + pow(m_pixelSize.y, 2));\n    const auto              DIAGONALIN = sqrt(pow(m_output->physicalSize.x / MMPERINCH, 2) + pow(m_output->physicalSize.y / MMPERINCH, 2));\n\n    const auto              PPI = DIAGONALPX / DIAGONALIN;\n\n    if (PPI > 200 /* High PPI, 2x*/)\n        return 2;\n    else if (PPI > 140 /* Medium PPI, 1.5x*/)\n        return 1.5;\n    return 1;\n}\n\nstatic bool shouldWraparound(const WORKSPACEID id1, const WORKSPACEID id2) {\n    static auto PWORKSPACEWRAPAROUND = CConfigValue<Hyprlang::INT>(\"animations:workspace_wraparound\");\n\n    if (!*PWORKSPACEWRAPAROUND)\n        return false;\n\n    WORKSPACEID lowestID  = INT64_MAX;\n    WORKSPACEID highestID = INT64_MIN;\n\n    for (auto const& w : g_pCompositor->getWorkspaces()) {\n        if (w->m_id < 0 || w->m_isSpecialWorkspace)\n            continue;\n        lowestID  = std::min(w->m_id, lowestID);\n        highestID = std::max(w->m_id, highestID);\n    }\n\n    return std::min(id1, id2) == lowestID && std::max(id1, id2) == highestID;\n}\n\nvoid CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bool noMouseMove, bool noFocus) {\n    if (!pWorkspace)\n        return;\n\n    if (pWorkspace->m_isSpecialWorkspace) {\n        if (m_activeSpecialWorkspace != pWorkspace) {\n            Log::logger->log(Log::DEBUG, \"changeworkspace on special, togglespecialworkspace to id {}\", pWorkspace->m_id);\n            setSpecialWorkspace(pWorkspace);\n        }\n        return;\n    }\n\n    if (pWorkspace == m_activeWorkspace)\n        return;\n\n    const auto POLDWORKSPACE = m_activeWorkspace;\n    m_activeWorkspace        = pWorkspace;\n\n    if (POLDWORKSPACE) {\n        POLDWORKSPACE->m_visible = false;\n        POLDWORKSPACE->m_events.activeChanged.emit();\n    }\n\n    pWorkspace->m_visible = true;\n\n    if (!internal) {\n        const auto ANIMTOLEFT = POLDWORKSPACE && (shouldWraparound(pWorkspace->m_id, POLDWORKSPACE->m_id) ^ (pWorkspace->m_id > POLDWORKSPACE->m_id));\n        const auto ANIMSTYLE  = pWorkspace->m_animationStyle;\n        if (POLDWORKSPACE)\n            g_pDesktopAnimationManager->startAnimation(POLDWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, ANIMTOLEFT, false, ANIMSTYLE);\n        g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, ANIMTOLEFT, false, ANIMSTYLE);\n\n        // move pinned windows\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (w->m_workspace == POLDWORKSPACE && w->m_pinned)\n                w->layoutTarget()->assignToSpace(pWorkspace->m_space);\n        }\n\n        if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace &&\n            !(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {\n            static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n            auto        pWindow      = pWorkspace->m_hasFullscreenWindow ? pWorkspace->getFullscreenWindow() : pWorkspace->getLastFocusedWindow();\n\n            if (!pWindow) {\n                if (*PFOLLOWMOUSE == 1)\n                    pWindow = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),\n                                                                   Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n\n                if (!pWindow)\n                    pWindow = pWorkspace->getTopLeftWindow();\n\n                if (!pWindow)\n                    pWindow = pWorkspace->getFirstWindow();\n            }\n\n            Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);\n        }\n\n        if (!noMouseMove)\n            g_pInputManager->simulateMouseMovement();\n\n        g_layoutManager->recalculateMonitor(m_self.lock());\n\n        g_pEventManager->postEvent(SHyprIPCEvent{\"workspace\", pWorkspace->m_name});\n        g_pEventManager->postEvent(SHyprIPCEvent{\"workspacev2\", std::format(\"{},{}\", pWorkspace->m_id, pWorkspace->m_name)});\n        Event::bus()->m_events.workspace.active.emit(pWorkspace);\n    }\n\n    // set all LSes as not above fullscreen on workspace changes\n    for (auto const& ls : g_pCompositor->m_layers) {\n        if (ls->m_monitor == m_self)\n            ls->m_aboveFullscreen = false;\n    }\n\n    pWorkspace->m_events.activeChanged.emit();\n\n    g_pHyprRenderer->damageMonitor(m_self.lock());\n\n    g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n        pWorkspace, pWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n    g_pConfigManager->ensureVRR(m_self.lock());\n\n    g_pCompositor->updateSuspendedStates();\n\n    if (m_activeSpecialWorkspace)\n        g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n            m_activeSpecialWorkspace, m_activeSpecialWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n}\n\nvoid CMonitor::changeWorkspace(const WORKSPACEID& id, bool internal, bool noMouseMove, bool noFocus) {\n    changeWorkspace(g_pCompositor->getWorkspaceByID(id), internal, noMouseMove, noFocus);\n}\n\nvoid CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {\n    if (m_activeSpecialWorkspace == pWorkspace)\n        return;\n\n    const auto POLDSPECIAL = m_activeSpecialWorkspace;\n\n    m_specialFade->setConfig(g_pConfigManager->getAnimationPropertyConfig(pWorkspace ? \"specialWorkspaceIn\" : \"specialWorkspaceOut\"));\n    *m_specialFade = pWorkspace ? 1.F : 0.F;\n\n    g_pHyprRenderer->damageMonitor(m_self.lock());\n\n    if (!pWorkspace) {\n        // remove special if exists\n        if (m_activeSpecialWorkspace) {\n            m_activeSpecialWorkspace->m_visible = false;\n            g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);\n            g_pEventManager->postEvent(SHyprIPCEvent{\"activespecial\", \",\" + m_name});\n            g_pEventManager->postEvent(SHyprIPCEvent{\"activespecialv2\", \",,\" + m_name});\n\n            // Reset layer surface state when closing special workspace\n            for (auto const& ls : g_pCompositor->m_layers) {\n                if (ls->m_monitor == m_self)\n                    ls->m_aboveFullscreen = false;\n            }\n        }\n        m_activeSpecialWorkspace.reset();\n\n        if (POLDSPECIAL)\n            POLDSPECIAL->m_events.activeChanged.emit();\n\n        g_layoutManager->recalculateMonitor(m_self.lock());\n\n        if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {\n            if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST)\n                Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);\n            else\n                g_pInputManager->refocus();\n        }\n\n        g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n            m_activeWorkspace, m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n        g_pConfigManager->ensureVRR(m_self.lock());\n\n        g_pCompositor->updateSuspendedStates();\n\n        return;\n    }\n\n    if (m_activeSpecialWorkspace) {\n        m_activeSpecialWorkspace->m_visible = false;\n        g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);\n    }\n\n    bool wasActive = false;\n    //close if open elsewhere\n    const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock();\n    if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {\n        PMWSOWNER->m_activeSpecialWorkspace.reset();\n        g_layoutManager->recalculateMonitor(PMWSOWNER);\n        g_pHyprRenderer->damageMonitor(PMWSOWNER);\n        g_pEventManager->postEvent(SHyprIPCEvent{\"activespecial\", \",\" + PMWSOWNER->m_name});\n        g_pEventManager->postEvent(SHyprIPCEvent{\"activespecialv2\", \",,\" + PMWSOWNER->m_name});\n\n        // Reset layer surfaces on the old monitor when special workspace is stolen\n        for (auto const& ls : g_pCompositor->m_layers) {\n            if (ls->m_monitor == PMWSOWNER)\n                ls->m_aboveFullscreen = false;\n        }\n\n        const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace;\n        g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,\n                                                               PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :\n                                                                                                                             CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n        wasActive = true;\n    }\n\n    // open special\n    pWorkspace->m_monitor               = m_self;\n    m_activeSpecialWorkspace            = pWorkspace;\n    m_activeSpecialWorkspace->m_visible = true;\n\n    // Reset layer surface state when opening special workspace\n    for (auto const& ls : g_pCompositor->m_layers) {\n        if (ls->m_monitor == m_self)\n            ls->m_aboveFullscreen = false;\n    }\n\n    if (POLDSPECIAL)\n        POLDSPECIAL->m_events.activeChanged.emit();\n\n    if (PMONITORWORKSPACEOWNER != m_self)\n        pWorkspace->m_events.monitorChanged.emit();\n\n    if (!wasActive)\n        pWorkspace->m_events.activeChanged.emit();\n\n    if (!wasActive)\n        g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true);\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace == pWorkspace) {\n            w->m_monitor = m_self;\n            w->updateSurfaceScaleTransformDetails();\n            w->setAnimationsToMove();\n\n            const auto MIDDLE = w->middle();\n            if (w->m_isFloating && VECNOTINRECT(MIDDLE, m_position.x, m_position.y, m_position.x + m_size.x, m_position.y + m_size.y) && !w->isX11OverrideRedirect()) {\n                // if it's floating and the middle isn't on the current mon, move it to the center\n                const auto PMONFROMMIDDLE = g_pCompositor->getMonitorFromVector(MIDDLE);\n                Vector2D   pos            = w->m_realPosition->goal();\n                if (VECNOTINRECT(MIDDLE, PMONFROMMIDDLE->m_position.x, PMONFROMMIDDLE->m_position.y, PMONFROMMIDDLE->m_position.x + PMONFROMMIDDLE->m_size.x,\n                                 PMONFROMMIDDLE->m_position.y + PMONFROMMIDDLE->m_size.y)) {\n                    // not on any monitor, center\n                    pos = middle() / 2.f - w->m_realSize->goal() / 2.f;\n                } else\n                    pos = pos - PMONFROMMIDDLE->m_position + m_position;\n\n                w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()});\n            }\n        }\n    }\n\n    g_layoutManager->recalculateMonitor(m_self.lock());\n\n    if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {\n        if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)\n            Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);\n        else\n            g_pInputManager->refocus();\n    }\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"activespecial\", pWorkspace->m_name + \",\" + m_name});\n    g_pEventManager->postEvent(SHyprIPCEvent{\"activespecialv2\", std::to_string(pWorkspace->m_id) + \",\" + pWorkspace->m_name + \",\" + m_name});\n\n    g_pHyprRenderer->damageMonitor(m_self.lock());\n\n    g_pDesktopAnimationManager->setFullscreenFadeAnimation(\n        pWorkspace, pWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);\n\n    g_pConfigManager->ensureVRR(m_self.lock());\n\n    g_pCompositor->updateSuspendedStates();\n}\n\nvoid CMonitor::setSpecialWorkspace(const WORKSPACEID& id) {\n    setSpecialWorkspace(g_pCompositor->getWorkspaceByID(id));\n}\n\nvoid CMonitor::moveTo(const Vector2D& pos) {\n    m_position = pos;\n}\n\nVector2D CMonitor::middle() {\n    return m_position + m_size / 2.f;\n}\n\nconst Mat3x3& CMonitor::getTransformMatrix() {\n    return m_projMatrix;\n}\n\nconst Mat3x3& CMonitor::getScaleMatrix() {\n    return m_projOutputMatrix;\n}\n\nvoid CMonitor::updateMatrix() {\n    m_projMatrix = Mat3x3::identity();\n    if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL)\n        m_projMatrix.translate(m_pixelSize / 2.0).transform(Math::wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0);\n\n    m_projOutputMatrix = Mat3x3::outputProjection(m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);\n}\n\nWORKSPACEID CMonitor::activeWorkspaceID() {\n    return m_activeWorkspace ? m_activeWorkspace->m_id : 0;\n}\n\nWORKSPACEID CMonitor::activeSpecialWorkspaceID() {\n    return m_activeSpecialWorkspace ? m_activeSpecialWorkspace->m_id : 0;\n}\n\nCBox CMonitor::logicalBox() {\n    return {m_position, m_size};\n}\n\nCBox CMonitor::logicalBoxMinusReserved() {\n    return m_reservedArea.apply(logicalBox());\n}\n\nvoid CMonitor::scheduleDone() {\n    if (m_doneScheduled)\n        return;\n\n    m_doneScheduled = true;\n\n    g_pEventLoopManager->doLater([M = m_self] {\n        if (!M) // if M is gone, we got destroyed, doesn't matter.\n            return;\n\n        if (!PROTO::outputs.contains(M->m_name))\n            return;\n\n        PROTO::outputs.at(M->m_name)->sendDone();\n        M->m_doneScheduled = false;\n    });\n}\n\nvoid CMonitor::setCTM(const Mat3x3& ctm_) {\n    m_ctm        = ctm_;\n    m_ctmUpdated = true;\n    g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::scheduleFrameReason::AQ_SCHEDULE_NEEDS_FRAME);\n}\n\nuint32_t CMonitor::isSolitaryBlocked(bool full) {\n    uint32_t reasons = 0;\n\n    if (g_pHyprNotificationOverlay->hasAny()) {\n        reasons |= SC_NOTIFICATION;\n        if (!full)\n            return reasons;\n    }\n\n    if (g_pHyprError->active() && Desktop::focusState()->monitor() == m_self) {\n        reasons |= SC_ERRORBAR;\n        if (!full)\n            return reasons;\n    }\n\n    if (g_pSessionLockManager->isSessionLocked()) {\n        reasons |= SC_LOCK;\n        if (!full)\n            return reasons;\n    }\n\n    const auto PWORKSPACE = m_activeWorkspace;\n    if (!PWORKSPACE) {\n        reasons |= SC_WORKSPACE;\n        return reasons;\n    }\n\n    if (!PWORKSPACE->m_hasFullscreenWindow) {\n        reasons |= SC_WINDOWED;\n        if (!full)\n            return reasons;\n    }\n\n    if (PROTO::data->dndActive()) {\n        reasons |= SC_DND;\n        if (!full)\n            return reasons;\n    }\n\n    if (m_activeSpecialWorkspace) {\n        reasons |= SC_SPECIAL;\n        if (!full)\n            return reasons;\n    }\n\n    if (PWORKSPACE->m_alpha->value() != 1.f) {\n        reasons |= SC_ALPHA;\n        if (!full)\n            return reasons;\n    }\n\n    if (PWORKSPACE->m_renderOffset->value() != Vector2D{}) {\n        reasons |= SC_OFFSET;\n        if (!full)\n            return reasons;\n    }\n\n    const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow();\n\n    if (!PCANDIDATE) {\n        reasons |= SC_CANDIDATE;\n        return reasons;\n    }\n\n    if (!PCANDIDATE->opaque()) {\n        reasons |= SC_OPAQUE;\n        if (!full)\n            return reasons;\n    }\n\n    if (PCANDIDATE->m_realSize->value() != m_size || PCANDIDATE->m_realPosition->value() != m_position || PCANDIDATE->m_realPosition->isBeingAnimated() ||\n        PCANDIDATE->m_realSize->isBeingAnimated()) {\n        reasons |= SC_TRANSFORM;\n        if (!full)\n            return reasons;\n    }\n\n    if (!m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) {\n        reasons |= SC_OVERLAYS;\n        if (!full)\n            return reasons;\n    }\n\n    for (auto const& topls : m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {\n        if (topls->m_alpha->value() != 0.f) {\n            reasons |= SC_OVERLAYS;\n            if (!full)\n                return reasons;\n        }\n    }\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || w->isHidden())\n            continue;\n\n        if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->m_createdOverFullscreen && w->visibleOnMonitor(m_self.lock())) {\n            reasons |= SC_FLOAT;\n            if (!full)\n                return reasons;\n        }\n    }\n\n    for (auto const& ws : g_pCompositor->getWorkspaces()) {\n        if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace || ws->m_monitor != m_self)\n            continue;\n\n        reasons |= SC_WORKSPACES;\n        if (!full)\n            return reasons;\n    }\n\n    // check if it did not open any subsurfaces or shit\n    if (!PCANDIDATE->getSolitaryResource())\n        reasons |= SC_SURFACES;\n\n    return reasons;\n}\n\nvoid CMonitor::recheckSolitary() {\n    m_solitaryClient.reset(); // reset it, if we find one it will be set.\n    if (isSolitaryBlocked())\n        return;\n\n    m_solitaryClient = m_activeWorkspace->getFullscreenWindow();\n}\n\nuint8_t CMonitor::isTearingBlocked(bool full) {\n    uint8_t     reasons = 0;\n\n    static auto PTEARINGENABLED = CConfigValue<Hyprlang::INT>(\"general:allow_tearing\");\n\n    if (!m_tearingState.nextRenderTorn) {\n        reasons |= TC_NOT_TORN;\n        if (!full)\n            return reasons;\n    }\n\n    if (!*PTEARINGENABLED) {\n        reasons |= TC_USER;\n        if (!full) {\n            Log::logger->log(Log::WARN, \"Tearing commit requested but the master switch general:allow_tearing is off, ignoring\");\n            return reasons;\n        }\n    }\n\n    if (g_pHyprRenderer->m_renderData.mouseZoomFactor != 1.0) {\n        reasons |= TC_ZOOM;\n        if (!full) {\n            Log::logger->log(Log::WARN, \"Tearing commit requested but scale factor is not 1, ignoring\");\n            return reasons;\n        }\n    }\n\n    if (!m_tearingState.canTear) {\n        reasons |= TC_SUPPORT;\n        if (!full) {\n            Log::logger->log(Log::WARN, \"Tearing commit requested but monitor doesn't support it, ignoring\");\n            return reasons;\n        }\n    }\n\n    // TODO: remove this when kernel allows tearing + hw cursor updated\n    if (g_pPointerManager->hasVisibleHWCursor(m_self.lock()))\n        reasons |= TC_HW_CURSOR;\n\n    if (m_solitaryClient.expired()) {\n        reasons |= TC_CANDIDATE;\n        return reasons;\n    }\n\n    if (!m_solitaryClient->canBeTorn())\n        reasons |= TC_WINDOW;\n\n    return reasons;\n}\n\nbool CMonitor::updateTearing() {\n    m_tearingState.activelyTearing = !isTearingBlocked();\n    m_tearingState.nextRenderTorn  = false;\n    return m_tearingState.activelyTearing;\n}\n\nuint16_t CMonitor::isDSBlocked(bool full) {\n    uint16_t    reasons        = 0;\n    static auto PDIRECTSCANOUT = CConfigValue<Hyprlang::INT>(\"render:direct_scanout\");\n    static auto PPASS          = CConfigValue<Hyprlang::INT>(\"render:cm_fs_passthrough\");\n    static auto PNONSHADER     = CConfigValue<Hyprlang::INT>(\"render:non_shader_cm\");\n\n    if (*PDIRECTSCANOUT == 0) {\n        reasons |= DS_BLOCK_USER;\n        if (!full)\n            return reasons;\n    }\n\n    if (*PDIRECTSCANOUT == 2) {\n        if (!m_activeWorkspace || !m_activeWorkspace->m_hasFullscreenWindow || m_activeWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN) {\n            reasons |= DS_BLOCK_WINDOWED;\n            if (!full)\n                return reasons;\n        } else if (m_activeWorkspace->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) {\n            reasons |= DS_BLOCK_CONTENT;\n            if (!full)\n                return reasons;\n        }\n    }\n\n    if (!m_mirrors.empty() || isMirror()) {\n        reasons |= DS_BLOCK_MIRROR;\n        if (!full)\n            return reasons;\n    }\n\n    if (g_pHyprRenderer->m_directScanoutBlocked) {\n        reasons |= DS_BLOCK_RECORD;\n        if (!full)\n            return reasons;\n    }\n\n    if (g_pPointerManager->softwareLockedFor(m_self.lock())) {\n        reasons |= DS_BLOCK_SW;\n        if (!full)\n            return reasons;\n    }\n\n    const auto PCANDIDATE = m_solitaryClient.lock();\n    if (!PCANDIDATE) {\n        reasons |= DS_BLOCK_CANDIDATE;\n        return reasons;\n    }\n\n    const auto PSURFACE = PCANDIDATE->getSolitaryResource();\n    if (!PSURFACE || !PSURFACE->m_current.texture || !PSURFACE->m_current.buffer) {\n        reasons |= DS_BLOCK_SURFACE;\n        return reasons;\n    }\n\n    if (PSURFACE->m_current.bufferSize != m_pixelSize || PSURFACE->m_current.transform != m_transform) {\n        reasons |= DS_BLOCK_TRANSFORM;\n        if (!full)\n            return reasons;\n    }\n\n    // we can't scanout shm buffers.\n    const auto params = PSURFACE->m_current.buffer->dmabuf();\n    if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) {\n        reasons |= DS_BLOCK_DMA;\n        if (!full)\n            return reasons;\n    }\n\n    const bool surfaceIsHDR   = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR();\n    const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB();\n\n    if (needsCM() && (*PNONSHADER != CM_NS_IGNORE || surfaceIsScRGB) && !canNoShaderCM() &&\n        ((inHDR() && (*PPASS == 0 || !surfaceIsHDR || surfaceIsScRGB)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))\n        reasons |= DS_BLOCK_CM;\n\n    return reasons;\n}\n\nbool CMonitor::attemptDirectScanout() {\n    static const auto PSAME     = CConfigValue<Hyprlang::INT>(\"debug:ds_handle_same_buffer\");\n    static const auto PSAMEFIFO = CConfigValue<Hyprlang::INT>(\"debug:ds_handle_same_buffer_fifo\");\n\n    const auto        blockedReason = isDSBlocked();\n    if (blockedReason)\n        return false;\n\n    const auto PCANDIDATE = m_solitaryClient.lock();\n    const auto PSURFACE   = PCANDIDATE->getSolitaryResource();\n    const auto params     = PSURFACE->m_current.buffer->dmabuf();\n\n    Log::logger->log(Log::TRACE, \"attemptDirectScanout: surface {:x} passed, will attempt, buffer {} fmt: {} -> {} (mod {})\", rc<uintptr_t>(PSURFACE.get()),\n                     rc<uintptr_t>(PSURFACE->m_current.buffer.m_buffer.get()), m_drmFormat, params.format, params.modifier);\n\n    auto PBUFFER = PSURFACE->m_current.buffer.m_buffer;\n\n    // #TODO this entire bit needs figuring out, vrr goes down the drain without it\n    if (PBUFFER == m_output->state->state().buffer && *PSAME) {\n        PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());\n\n        if (m_scanoutNeedsCursorUpdate) {\n            if (!m_state.test()) {\n                Log::logger->log(Log::TRACE, \"attemptDirectScanout: failed basic test on cursor update\");\n                return false;\n            }\n\n            if (!m_output->commit()) {\n                Log::logger->log(Log::TRACE, \"attemptDirectScanout: failed to commit cursor update\");\n                m_lastScanout.reset();\n                return false;\n            }\n\n            m_scanoutNeedsCursorUpdate = false;\n        }\n\n        //#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked.\n        if (PSURFACE->m_fifo && !m_tearingState.activelyTearing && *PSAMEFIFO)\n            PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);\n\n        return true;\n    }\n\n    // FIXME: make sure the buffer actually follows the available scanout dmabuf formats\n    // and comes from the appropriate device. This may implode on multi-gpu!!\n\n    // entering into scanout, so save monitor format\n    if (m_lastScanout.expired())\n        m_prevDrmFormat = m_drmFormat;\n\n    if (m_drmFormat != params.format) {\n        m_output->state->setFormat(params.format);\n        m_drmFormat = params.format;\n    }\n\n    m_output->state->setBuffer(PBUFFER);\n    Log::logger->log(Log::TRACE, \"attemptDirectScanout: setting presentation mode\");\n    m_output->state->setPresentationMode(m_tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :\n                                                                          Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);\n\n    if (!m_state.test()) {\n        Log::logger->log(Log::TRACE, \"attemptDirectScanout: failed basic test\");\n        return false;\n    }\n\n    PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());\n\n    m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage());\n\n    // multigpu needs a fence to trigger fence syncing blits and also committing with the recreated dgpu fence\n    if (!DRM::sameGpu(m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd) && g_pHyprRenderer->explicitSyncSupported()) {\n        auto sync = CEGLSync::create();\n\n        if (sync->fd().isValid()) {\n            m_inFence = sync->takeFd();\n            m_output->state->setExplicitInFence(m_inFence.get());\n        } else\n            m_output->state->resetExplicitFences(); // good luck.\n    } else\n        m_output->state->resetExplicitFences();\n\n    // no need to do explicit sync here as surface current can only ever be ready to read\n\n    bool ok = m_output->commit();\n\n    if (!ok) {\n        Log::logger->log(Log::TRACE, \"attemptDirectScanout: failed to scanout surface\");\n        m_lastScanout.reset();\n        return false;\n    }\n\n    if (m_lastScanout.expired()) {\n        m_lastScanout = PCANDIDATE;\n        Log::logger->log(Log::DEBUG, \"Entered a direct scanout to {:x}: \\\"{}\\\"\", rc<uintptr_t>(PCANDIDATE.get()), PCANDIDATE->m_title);\n    }\n\n    m_scanoutNeedsCursorUpdate = false;\n\n    if (!PBUFFER->lockedByBackend || PBUFFER->m_hlEvents.backendRelease)\n        return true;\n\n    // lock buffer while DRM/KMS is using it, then release it when page flip happens since DRM/KMS should be done by then\n    // btw buffer's syncReleaser will take care of signaling release point, so we don't do that here\n    PBUFFER->lock();\n    PBUFFER->onBackendRelease([wb = WP<IHLBuffer>{PBUFFER}] {\n        if (wb)\n            wb->unlock();\n    });\n\n    return true;\n}\n\nvoid CMonitor::setDPMS(bool on) {\n    // Don't trigger animation if the target state is the same\n    if (m_dpmsStatus == on)\n        return;\n\n    m_dpmsStatus = on;\n    m_events.dpmsChanged.emit();\n\n    if (on) {\n        // enable the monitor. Wait for the frame to be presented, then begin animation\n        m_dpmsBlackOpacity->setCallbackOnEnd(nullptr);\n        m_dpmsBlackOpacity->setValueAndWarp(1.F);\n        m_pendingDpmsAnimation        = true;\n        m_pendingDpmsAnimationCounter = 0;\n        commitDPMSState(true);\n    } else {\n        // disable the monitor. Begin the animation, then do dpms on its end.\n        m_dpmsBlackOpacity->setCallbackOnEnd(nullptr);\n        m_dpmsBlackOpacity->setValueAndWarp(0.F);\n        *m_dpmsBlackOpacity = 1.F;\n        m_dpmsBlackOpacity->setCallbackOnEnd(\n            [this, self = m_self](auto) {\n                if (!self)\n                    return;\n\n                // commit DPMS to disable the monitor, it's fully black now\n                commitDPMSState(false);\n            },\n            true);\n    }\n}\n\nvoid CMonitor::commitDPMSState(bool state) {\n    m_output->state->resetExplicitFences();\n    m_output->state->setEnabled(state);\n\n    if (!m_state.commit()) {\n        Log::logger->log(Log::ERR, \"Couldn't commit output {} for DPMS = {}, will retry.\", m_name, state);\n\n        // retry in 2 frames. This could happen when the DRM backend rejects our commit\n        // because disable + enable were sent almost instantly\n\n        m_dpmsRetryTimer = makeShared<CEventLoopTimer>(\n            std::chrono::milliseconds(2000 / sc<int>(m_refreshRate)),\n            [this, self = m_self](SP<CEventLoopTimer> s, void* d) {\n                if (!self)\n                    return;\n\n                m_output->state->resetExplicitFences();\n                m_output->state->setEnabled(m_dpmsStatus);\n                if (!m_state.commit()) {\n                    Log::logger->log(Log::ERR, \"Couldn't retry committing output {} for DPMS = {}\", m_name, m_dpmsStatus);\n                    return;\n                }\n\n                m_dpmsRetryTimer.reset();\n            },\n            nullptr);\n        g_pEventLoopManager->addTimer(m_dpmsRetryTimer);\n\n        return;\n    }\n\n    if (state)\n        g_pHyprRenderer->damageMonitor(m_self.lock());\n}\n\nvoid CMonitor::debugLastPresentation(const std::string& message) {\n    Log::logger->log(Log::TRACE, \"{} (last presentation {} - {} fps)\", message, m_lastPresentationTimer.getMillis(),\n                     m_lastPresentationTimer.getMillis() > 0 ? 1000.0f / m_lastPresentationTimer.getMillis() : 0.0f);\n}\n\nvoid CMonitor::onCursorMovedOnMonitor() {\n    if (!m_tearingState.activelyTearing || !m_solitaryClient || !g_pHyprRenderer->shouldRenderCursor())\n        return;\n\n    // submit a frame immediately. This will only update the cursor pos.\n    // output->state->setBuffer(output->state->state().buffer);\n    // output->state->addDamage(CRegion{});\n    // output->state->setPresentationMode(Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE);\n    // if (!output->commit())\n    //     Log::logger->log(Log::ERR, \"onCursorMovedOnMonitor: tearing and wanted to update cursor, failed.\");\n\n    // FIXME: try to do the above. We currently can't just render because drm is a fucking bitch\n    // and throws a \"nO pRoP cAn Be ChAnGeD dUrInG AsYnC fLiP\" on crtc_x\n    // this will throw too but fix it if we use sw cursors\n\n    m_tearingState.frameScheduledWhileBusy = true;\n}\n\nbool CMonitor::supportsWideColor() {\n    switch (m_supportsWideColor) {\n        case -1: return false;\n        case 1: return true;\n        default: return m_output->parsedEDID.supportsBT2020;\n    }\n}\n\nbool CMonitor::supportsHDR() {\n    if (!supportsWideColor())\n        return false;\n\n    switch (m_supportsHDR) {\n        case -1: return false;\n        case 1: return true;\n        default: return m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->supportsPQ : false;\n    }\n}\n\nfloat CMonitor::minLuminance(float defaultValue) {\n    return m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : defaultValue);\n}\n\nint CMonitor::maxLuminance(int defaultValue) {\n    return m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : defaultValue);\n}\n\nint CMonitor::maxAvgLuminance(int defaultValue) {\n    return m_maxAvgLuminance >= 0 ? m_maxAvgLuminance :\n                                    (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : defaultValue);\n}\n\nfloat CMonitor::maxFALL() {\n    return m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : 0);\n}\n\nfloat CMonitor::maxCLL() {\n    return m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0);\n}\n\nbool CMonitor::wantsWideColor() {\n    return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020);\n}\n\nbool CMonitor::wantsHDR() {\n    return supportsHDR() && inHDR();\n}\n\nbool CMonitor::inHDR() {\n    return m_output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2;\n}\n\nbool CMonitor::inFullscreenMode() {\n    // Check special workspace first since it renders on top of regular workspaces\n    if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)\n        return true;\n    return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN;\n}\n\nPHLWINDOW CMonitor::getFullscreenWindow() {\n    // Check special workspace first since it renders on top of regular workspaces\n    if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)\n        return m_activeSpecialWorkspace->getFullscreenWindow();\n    if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)\n        return m_activeWorkspace->getFullscreenWindow();\n    return nullptr;\n}\n\nstd::optional<NColorManagement::PImageDescription> CMonitor::getFSImageDescription() {\n    if (!inFullscreenMode())\n        return {};\n\n    const auto FS_WINDOW = getFullscreenWindow();\n    if (!FS_WINDOW)\n        return {};\n\n    const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();\n    const auto SURF      = ROOT_SURF->findWithCM();\n    return SURF ? NColorManagement::CImageDescription::from(SURF->m_colorManagement->imageDescription()) : DEFAULT_IMAGE_DESCRIPTION;\n}\n\nNColorManagement::SPCPRimaries CMonitor::getMasteringPrimaries() {\n    return m_output->parsedEDID.chromaticityCoords.has_value() ?\n        NColorManagement::SPCPRimaries{\n            .red   = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},\n            .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},\n            .blue  = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},\n            .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},\n        } :\n        NColorManagement::SPCPRimaries{};\n}\n\nNColorManagement::SImageDescription::SPCMasteringLuminances CMonitor::getMasteringLuminances() {\n    return {\n        .min = m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : 0),\n        .max = m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0),\n    };\n}\n\nuint32_t CMonitor::getPreferredReadFormat() {\n    static const auto PFORCE8BIT = CConfigValue<Hyprlang::INT>(\"misc:screencopy_force_8b\");\n\n    auto              monFmt = m_output->state->state().drmFormat;\n\n    if (*PFORCE8BIT)\n        if (monFmt == DRM_FORMAT_BGRA1010102 || monFmt == DRM_FORMAT_ARGB2101010 || monFmt == DRM_FORMAT_XRGB2101010 || monFmt == DRM_FORMAT_BGRX1010102 ||\n            monFmt == DRM_FORMAT_XBGR2101010)\n            monFmt = DRM_FORMAT_XRGB8888;\n\n    return monFmt;\n}\n\nbool CMonitor::needsCM() {\n    const auto SRC_DESC = getFSImageDescription();\n    return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription;\n}\n\n// TODO support more drm properties\nbool CMonitor::canNoShaderCM() {\n    static auto PNONSHADER = CConfigValue<Hyprlang::INT>(\"render:non_shader_cm\");\n    if (*PNONSHADER == CM_NS_DISABLE)\n        return false;\n\n    const auto SRC_DESC = getFSImageDescription();\n    if (!SRC_DESC.has_value())\n        return false;\n\n    if (SRC_DESC.value() == m_imageDescription)\n        return true; // no CM needed\n\n    const auto SRC_DESC_VALUE = SRC_DESC.value()->value();\n\n    if (m_imageDescription->value().icc.present)\n        return false;\n\n    const auto sdrEOTF = NTransferFunction::fromConfig();\n    // only primaries differ\n    return (\n        (SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction ||\n         (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&\n          m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) &&\n        SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower &&\n        (!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances)\n        // not used by shaders atm\n        // && SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL\n    );\n}\n\nbool CMonitor::doesNoShaderCM() {\n    return m_noShaderCTM;\n}\n\nstatic std::vector<uint16_t> resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) {\n    std::vector<uint16_t> out;\n    out.resize(gammaSize * 3);\n\n    //\n    auto sample = [&](int c, float x) -> uint16_t {\n        const float maxX = t.entries - 1;\n        x                = std::clamp(x, 0.F, maxX);\n\n        const size_t i0 = (size_t)std::floor(x);\n        const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1);\n        const float  f  = x - sc<float>(i0);\n\n        const float  v0 = sc<float>(t.ch[c][i0]);\n        const float  v1 = sc<float>(t.ch[c][i1]);\n        const float  v  = v0 + ((v1 - v0) * f);\n\n        int64_t      vi = std::round(v);\n        vi              = std::clamp(vi, sc<int64_t>(0), sc<int64_t>(65535));\n        return sc<uint16_t>(vi);\n    };\n\n    for (size_t i = 0; i < gammaSize; ++i) {\n        float          x = sc<float>(i) * sc<float>(t.entries - 1) / sc<float>(gammaSize - 1);\n\n        const uint16_t r = sample(0, x);\n        const uint16_t g = sample(1, x);\n        const uint16_t b = sample(2, x);\n\n        out[i * 3 + 0] = r;\n        out[i * 3 + 1] = g;\n        out[i * 3 + 2] = b;\n    }\n\n    return out;\n}\n\nvoid CMonitor::updateVCGTRamps() {\n    auto gammaSize = m_output->getGammaSize();\n\n    if (gammaSize <= 10) {\n        Log::logger->log(Log::DEBUG, \"CMonitor::updateVCGTRamps: skipping, no gamma ramp for output\");\n        return;\n    }\n\n    if (!m_imageDescription->value().icc.vcgt) {\n        if (m_vcgtRampsSet)\n            m_output->state->setGammaLut({});\n\n        m_vcgtRampsSet = false;\n        return;\n    }\n\n    // build table\n    auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize);\n\n    m_output->state->setGammaLut(table);\n\n    m_vcgtRampsSet = true;\n}\n\nbool CMonitor::gammaRampsInUse() {\n    return m_vcgtRampsSet;\n}\n\nCMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) {\n    ;\n}\n\nvoid CMonitorState::ensureBufferPresent() {\n    const auto STATE = m_owner->m_output->state->state();\n    if (!STATE.enabled) {\n        Log::logger->log(Log::TRACE, \"CMonitorState::ensureBufferPresent: Ignoring, monitor is not enabled\");\n        return;\n    }\n\n    if (STATE.buffer) {\n        if (const auto params = STATE.buffer->dmabuf(); params.success && params.format == m_owner->m_drmFormat)\n            return;\n    }\n\n    // this is required for modesetting being possible and might be missing in case of first tests in the renderer\n    // where we test modes and buffers\n    Log::logger->log(Log::DEBUG, \"CMonitorState::ensureBufferPresent: no buffer or mismatched format, attaching one from the swapchain for modeset being possible\");\n    m_owner->m_output->state->setBuffer(m_owner->m_output->swapchain->next(nullptr));\n    m_owner->m_output->swapchain->rollback(); // restore the counter, don't advance the swapchain\n}\n\nbool CMonitorState::commit() {\n    if (!updateSwapchain())\n        return false;\n\n    Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock());\n\n    ensureBufferPresent();\n\n    bool ret = m_owner->m_output->commit();\n    return ret;\n}\n\nbool CMonitorState::test() {\n    if (!updateSwapchain())\n        return false;\n\n    ensureBufferPresent();\n\n    return m_owner->m_output->test();\n}\n\nbool CMonitorState::updateSwapchain() {\n    auto        options = m_owner->m_output->swapchain->currentOptions();\n    const auto& STATE   = m_owner->m_output->state->state();\n    const auto& MODE    = STATE.mode ? STATE.mode : STATE.customMode;\n    if (!MODE) {\n        Log::logger->log(Log::WARN, \"updateSwapchain: No mode?\");\n        return true;\n    }\n    options.format  = m_owner->m_drmFormat;\n    options.scanout = true;\n    options.length  = 3;\n    options.size    = MODE->pixelSize;\n    return m_owner->m_output->swapchain->reconfigure(options);\n}\n"
  },
  {
    "path": "src/helpers/Monitor.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include <stack>\n#include <vector>\n#include \"../SharedDefs.hpp\"\n#include \"MiscFunctions.hpp\"\n#include \"WLClasses.hpp\"\n#include <array>\n#include \"AnimatedVariable.hpp\"\n#include \"CMType.hpp\"\n\n#include <xf86drmMode.h>\n#include \"MonitorZoomController.hpp\"\n#include \"../render/Texture.hpp\"\n#include \"../render/Framebuffer.hpp\"\n#include \"time/Timer.hpp\"\n#include \"math/Math.hpp\"\n#include \"../desktop/reserved/ReservedArea.hpp\"\n#include <optional>\n#include \"cm/ColorManagement.hpp\"\n#include \"signal/Signal.hpp\"\n#include \"DamageRing.hpp\"\n#include <aquamarine/output/Output.hpp>\n#include <aquamarine/allocator/Swapchain.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n\n#include \"../helpers/TransferFunction.hpp\"\n\nclass CMonitorFrameScheduler;\n\n// Enum for the different types of auto directions, e.g. auto-left, auto-up.\nenum eAutoDirs : uint8_t {\n    DIR_AUTO_NONE = 0, /* None will be treated as right. */\n    DIR_AUTO_UP,\n    DIR_AUTO_DOWN,\n    DIR_AUTO_LEFT,\n    DIR_AUTO_RIGHT,\n    DIR_AUTO_CENTER_UP,\n    DIR_AUTO_CENTER_DOWN,\n    DIR_AUTO_CENTER_LEFT,\n    DIR_AUTO_CENTER_RIGHT\n};\n\nstruct SMonitorRule {\n    eAutoDirs              autoDir       = DIR_AUTO_NONE;\n    std::string            name          = \"\";\n    Vector2D               resolution    = Vector2D(1280, 720);\n    Vector2D               offset        = Vector2D(0, 0);\n    float                  scale         = 1;\n    float                  refreshRate   = 60; // Hz\n    bool                   disabled      = false;\n    wl_output_transform    transform     = WL_OUTPUT_TRANSFORM_NORMAL;\n    std::string            mirrorOf      = \"\";\n    bool                   enable10bit   = false;\n    NCMType::eCMType       cmType        = NCMType::CM_SRGB;\n    NTransferFunction::eTF sdrEotf       = NTransferFunction::TF_DEFAULT;\n    float                  sdrSaturation = 1.0f; // SDR -> HDR\n    float                  sdrBrightness = 1.0f; // SDR -> HDR\n    Desktop::CReservedArea reservedArea;\n    std::string            iccFile;\n\n    int                    supportsWideColor = 0;    // 0 - auto, 1 - force enable, -1 - force disable\n    int                    supportsHDR       = 0;    // 0 - auto, 1 - force enable, -1 - force disable\n    float                  sdrMinLuminance   = 0.2f; // SDR -> HDR\n    int                    sdrMaxLuminance   = 80;   // SDR -> HDR\n\n    // Incorrect values will result in reduced luminance range or incorrect tonemapping. Shouldn't damage the HW. Use with care in case of a faulty monitor firmware.\n    float              minLuminance    = -1.0f; // >= 0 overrides EDID\n    int                maxLuminance    = -1;    // >= 0 overrides EDID\n    int                maxAvgLuminance = -1;    // >= 0 overrides EDID\n\n    drmModeModeInfo    drmMode = {};\n    std::optional<int> vrr;\n};\n\nclass CMonitor;\nclass CSyncTimeline;\nclass CEGLSync;\nclass CEventLoopTimer;\n\nclass CMonitorState {\n  public:\n    CMonitorState(CMonitor* owner);\n    ~CMonitorState() = default;\n\n    bool commit();\n    bool test();\n    bool updateSwapchain();\n\n  private:\n    void      ensureBufferPresent();\n\n    CMonitor* m_owner = nullptr;\n};\n\nclass CMonitor {\n  public:\n    CMonitor(SP<Aquamarine::IOutput> output);\n    ~CMonitor();\n\n    Vector2D                    m_position         = Vector2D(-1, -1); // means unset\n    Vector2D                    m_xwaylandPosition = Vector2D(-1, -1); // means unset\n    eAutoDirs                   m_autoDir          = DIR_AUTO_NONE;\n    Vector2D                    m_size             = Vector2D(0, 0);\n    Vector2D                    m_pixelSize        = Vector2D(0, 0);\n    Vector2D                    m_transformedSize  = Vector2D(0, 0);\n\n    MONITORID                   m_id                     = MONITOR_INVALID;\n    PHLWORKSPACE                m_activeWorkspace        = nullptr;\n    PHLWORKSPACE                m_activeSpecialWorkspace = nullptr;\n    float                       m_setScale               = 1; // scale set by cfg\n    float                       m_scale                  = 1; // real scale\n\n    std::string                 m_name             = \"\";\n    std::string                 m_description      = \"\";\n    std::string                 m_shortDescription = \"\";\n\n    drmModeModeInfo             m_customDrmMode = {};\n\n    Desktop::CReservedArea      m_reservedArea;\n\n    CMonitorState               m_state;\n    CDamageRing                 m_damage;\n\n    SP<Aquamarine::IOutput>     m_output;\n    float                       m_refreshRate     = 60; // Hz\n    int                         m_forceFullFrames = 0;\n    bool                        m_scheduledRecalc = false;\n    wl_output_transform         m_transform       = WL_OUTPUT_TRANSFORM_NORMAL;\n    float                       m_xwaylandScale   = 1.f;\n\n    std::optional<Vector2D>     m_forceSize;\n    SP<Aquamarine::SOutputMode> m_currentMode;\n    SP<Aquamarine::CSwapchain>  m_cursorSwapchain;\n    uint32_t                    m_drmFormat     = DRM_FORMAT_INVALID;\n    uint32_t                    m_prevDrmFormat = DRM_FORMAT_INVALID;\n\n    CMonitorZoomController      m_zoomController;\n\n    bool                        m_dpmsStatus       = true;\n    bool                        m_vrrActive        = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.\n    bool                        m_enabled10bit     = false; // as above, this can be TRUE even if 10 bit failed.\n    NCMType::eCMType            m_cmType           = NCMType::CM_SRGB;\n    NTransferFunction::eTF      m_sdrEotf          = NTransferFunction::TF_DEFAULT;\n    float                       m_sdrSaturation    = 1.0f;\n    float                       m_sdrBrightness    = 1.0f;\n    float                       m_sdrMinLuminance  = 0.2f;\n    int                         m_sdrMaxLuminance  = 80;\n    bool                        m_createdByUser    = false;\n    bool                        m_isUnsafeFallback = false;\n\n    SP<CEventLoopTimer>         m_dpmsRetryTimer;\n\n    bool                        m_pendingFrame    = false; // if we schedule a frame during rendering, reschedule it after\n    bool                        m_renderingActive = false;\n\n    bool                        m_ratsScheduled = false;\n    CTimer                      m_lastPresentationTimer;\n\n    bool                        m_isBeingLeased = false;\n\n    SMonitorRule                m_activeMonitorRule;\n\n    SP<ITexture>                m_splash;\n    SP<ITexture>                m_background;\n\n    // explicit sync\n    Hyprutils::OS::CFileDescriptor m_inFence; // TODO: remove when aq uses CFileDescriptor\n\n    PHLMONITORREF                  m_self;\n\n    UP<CMonitorFrameScheduler>     m_frameScheduler;\n\n    // mirroring\n    PHLMONITORREF              m_mirrorOf;\n    std::vector<PHLMONITORREF> m_mirrors;\n    SP<IFramebuffer>           m_monitorMirrorFB;\n\n    // rendering fb\n    SP<IFramebuffer> m_offloadFB;\n    SP<IFramebuffer> m_mirrorFB;     // these are used for some effects,\n    SP<IFramebuffer> m_mirrorSwapFB; // etc\n    SP<IFramebuffer> m_offMainFB;\n    SP<IFramebuffer> m_blurFB;\n    SP<ITexture>     m_stencilTex;\n\n    // ctm\n    Mat3x3 m_ctm        = Mat3x3::identity();\n    bool   m_ctmUpdated = false;\n\n    // for tearing\n    PHLWINDOWREF m_solitaryClient;\n\n    // for direct scanout\n    PHLWINDOWREF m_lastScanout;\n    bool         m_directScanoutIsActive    = false; // for cleanup logic. m_lastScanout.expired() can become true before the DS cleanup if client crashes/exits while DS is active.\n    bool         m_scanoutNeedsCursorUpdate = false;\n\n    // for special fade/blur\n    PHLANIMVAR<float> m_specialFade;\n\n    // for dpms off anim\n    PHLANIMVAR<float> m_dpmsBlackOpacity;\n    bool              m_pendingDpmsAnimation        = false;\n    int               m_pendingDpmsAnimationCounter = 0;\n\n    PHLANIMVAR<float> m_cursorZoom;\n\n    // for fading in the wallpaper because it doesn't happen instantly (it's loaded async)\n    PHLANIMVAR<float> m_backgroundOpacity;\n\n    // for initial zoom anim\n    PHLANIMVAR<float> m_zoomAnimProgress;\n    CTimer            m_newMonitorAnimTimer;\n    int               m_zoomAnimFrameCounter = 0;\n\n    struct {\n        bool canTear         = false;\n        bool nextRenderTorn  = false;\n        bool activelyTearing = false;\n\n        bool busy                    = false;\n        bool frameScheduledWhileBusy = false;\n    } m_tearingState;\n\n    struct {\n        CSignalT<> commit;\n        CSignalT<> destroy;\n        CSignalT<> connect;\n        CSignalT<> disconnect;\n        CSignalT<> dpmsChanged;\n        CSignalT<> modeChanged;\n        CSignalT<> presented;\n    } m_events;\n\n    std::array<std::vector<PHLLSREF>, 4> m_layerSurfaceLayers;\n\n    // keep in sync with HyprCtl\n    enum eDSBlockReason : uint16_t {\n        DS_OK = 0,\n\n        DS_BLOCK_UNKNOWN   = (1 << 0),\n        DS_BLOCK_USER      = (1 << 1),\n        DS_BLOCK_WINDOWED  = (1 << 2),\n        DS_BLOCK_CONTENT   = (1 << 3),\n        DS_BLOCK_MIRROR    = (1 << 4),\n        DS_BLOCK_RECORD    = (1 << 5),\n        DS_BLOCK_SW        = (1 << 6),\n        DS_BLOCK_CANDIDATE = (1 << 7),\n        DS_BLOCK_SURFACE   = (1 << 8),\n        DS_BLOCK_TRANSFORM = (1 << 9),\n        DS_BLOCK_DMA       = (1 << 10),\n        DS_BLOCK_FAILED    = (1 << 11),\n        DS_BLOCK_CM        = (1 << 12),\n\n        DS_CHECKS_COUNT = 14,\n    };\n\n    // keep in sync with HyprCtl\n    enum eSolitaryCheck : uint32_t {\n        SC_OK = 0,\n\n        SC_UNKNOWN      = (1 << 0),\n        SC_NOTIFICATION = (1 << 1),\n        SC_LOCK         = (1 << 2),\n        SC_WORKSPACE    = (1 << 3),\n        SC_WINDOWED     = (1 << 4),\n        SC_DND          = (1 << 5),\n        SC_SPECIAL      = (1 << 6),\n        SC_ALPHA        = (1 << 7),\n        SC_OFFSET       = (1 << 8),\n        SC_CANDIDATE    = (1 << 9),\n        SC_OPAQUE       = (1 << 10),\n        SC_TRANSFORM    = (1 << 11),\n        SC_OVERLAYS     = (1 << 12),\n        SC_FLOAT        = (1 << 13),\n        SC_WORKSPACES   = (1 << 14),\n        SC_SURFACES     = (1 << 15),\n        SC_ERRORBAR     = (1 << 16),\n\n        SC_CHECKS_COUNT = 17,\n    };\n\n    // keep in sync with HyprCtl\n    enum eTearingCheck : uint8_t {\n        TC_OK = 0,\n\n        TC_UNKNOWN   = (1 << 0),\n        TC_NOT_TORN  = (1 << 1),\n        TC_USER      = (1 << 2),\n        TC_ZOOM      = (1 << 3),\n        TC_SUPPORT   = (1 << 4),\n        TC_CANDIDATE = (1 << 5),\n        TC_WINDOW    = (1 << 6),\n        TC_HW_CURSOR = (1 << 7),\n\n        TC_CHECKS_COUNT = 8,\n    };\n\n    // methods\n    void        onConnect(bool noRule);\n    void        onDisconnect(bool destroy = false);\n    void        applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf);\n    bool        applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false);\n    void        addDamage(const pixman_region32_t* rg);\n    void        addDamage(const CRegion& rg);\n    void        addDamage(const CBox& box);\n    bool        shouldSkipScheduleFrameOnMouseEvent();\n    void        setMirror(const std::string&);\n    bool        isMirror();\n    bool        matchesStaticSelector(const std::string& selector) const;\n    float       getDefaultScale();\n    void        changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false);\n    void        changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false);\n    void        setSpecialWorkspace(const PHLWORKSPACE& pWorkspace);\n    void        setSpecialWorkspace(const WORKSPACEID& id);\n    void        moveTo(const Vector2D& pos);\n    Vector2D    middle();\n    WORKSPACEID activeWorkspaceID();\n    WORKSPACEID activeSpecialWorkspaceID();\n    CBox        logicalBox();\n    CBox        logicalBoxMinusReserved();\n    void        scheduleDone();\n    uint32_t    isSolitaryBlocked(bool full = false);\n    void        recheckSolitary();\n    uint8_t     isTearingBlocked(bool full = false);\n    bool        updateTearing();\n    uint16_t    isDSBlocked(bool full = false);\n    bool        attemptDirectScanout();\n    void        setCTM(const Mat3x3& ctm);\n    void        onCursorMovedOnMonitor();\n    void        setDPMS(bool on);\n\n    //\n    const Mat3x3& getTransformMatrix();\n    const Mat3x3& getScaleMatrix();\n\n    void          debugLastPresentation(const std::string& message);\n\n    bool          supportsWideColor();\n    bool          supportsHDR();\n    float         minLuminance(float defaultValue = 0);\n    int           maxLuminance(int defaultValue = 80);\n    int           maxAvgLuminance(int defaultValue = 80);\n    float         maxFALL();\n    float         maxCLL();\n\n    bool          wantsWideColor();\n    bool          wantsHDR();\n\n    bool          inHDR();\n    bool          gammaRampsInUse();\n\n    /// Has an active workspace with a real fullscreen window (includes special workspace)\n    bool inFullscreenMode();\n    /// Get fullscreen window from active or special workspace\n    PHLWINDOW                                                   getFullscreenWindow();\n    std::optional<NColorManagement::PImageDescription>          getFSImageDescription();\n\n    NColorManagement::SPCPRimaries                              getMasteringPrimaries();\n    NColorManagement::SImageDescription::SPCMasteringLuminances getMasteringLuminances();\n\n    uint32_t                                                    getPreferredReadFormat();\n\n    bool                                                        needsCM();\n    /// Can do CM without shader\n    bool                                canNoShaderCM();\n    bool                                doesNoShaderCM();\n\n    bool                                m_enabled             = false;\n    bool                                m_renderingInitPassed = false;\n\n    PHLWINDOWREF                        m_previousFSWindow;\n    bool                                m_needsHDRupdate = false;\n\n    NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{});\n    bool                                m_noShaderCTM      = false; // sets drm CTM, restore needed\n\n    bool                                m_blurFBDirty        = true;\n    bool                                m_blurFBShouldRender = false;\n\n    // For the list lookup\n\n    bool operator==(const CMonitor& rhs) {\n        return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name;\n    }\n\n  private:\n    void                    updateMatrix();\n    Mat3x3                  m_projMatrix;\n    Mat3x3                  m_projOutputMatrix;\n\n    void                    setupDefaultWS(const SMonitorRule&);\n    WORKSPACEID             findAvailableDefaultWS();\n    void                    commitDPMSState(bool state);\n    void                    updateVCGTRamps();\n\n    bool                    m_doneScheduled = false;\n    bool                    m_vcgtRampsSet  = false;\n    std::stack<WORKSPACEID> m_prevWorkSpaces;\n\n    struct {\n        CHyprSignalListener frame;\n        CHyprSignalListener destroy;\n        CHyprSignalListener state;\n        CHyprSignalListener needsFrame;\n        CHyprSignalListener presented;\n        CHyprSignalListener commit;\n    } m_listeners;\n\n    int   m_supportsWideColor = 0;\n    int   m_supportsHDR       = 0;\n    float m_minLuminance      = -1.0f;\n    int   m_maxLuminance      = -1;\n    int   m_maxAvgLuminance   = -1;\n};\n"
  },
  {
    "path": "src/helpers/MonitorFrameScheduler.cpp",
    "content": "#include \"MonitorFrameScheduler.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n\nCMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) {\n    ;\n}\n\nbool CMonitorFrameScheduler::newSchedulingEnabled() {\n    static auto PENABLENEW = CConfigValue<Hyprlang::INT>(\"render:new_render_scheduling\");\n\n    return *PENABLENEW && g_pHyprRenderer->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive;\n}\n\nvoid CMonitorFrameScheduler::onSyncFired() {\n\n    if (!newSchedulingEnabled())\n        return;\n\n    // Sync fired: reset submitted state, set as rendered. Check the last render time. If we are running\n    // late, we will instantly render here.\n\n    if (std::chrono::duration_cast<std::chrono::microseconds>(hrc::now() - m_lastRenderBegun).count() / 1000.F < 1000.F / m_monitor->m_refreshRate) {\n        // we are in. Frame is valid. We can just render as normal.\n        Log::logger->log(Log::TRACE, \"CMonitorFrameScheduler: {} -> onSyncFired, didn't miss.\", m_monitor->m_name);\n        m_renderAtFrame = true;\n        return;\n    }\n\n    Log::logger->log(Log::TRACE, \"CMonitorFrameScheduler: {} -> onSyncFired, missed.\", m_monitor->m_name);\n\n    // we are out. The frame is taking too long to render. Begin rendering immediately, but don't commit yet.\n    m_pendingThird  = true;\n    m_renderAtFrame = false; // block frame rendering, we already scheduled\n\n    m_lastRenderBegun = hrc::now();\n\n    // get a ref to ourselves. renderMonitor can destroy this scheduler if it decides to perform a monitor reload\n    // FIXME: this is horrible. \"renderMonitor\" should not be able to do that.\n    auto self = m_self;\n\n    g_pHyprRenderer->renderMonitor(m_monitor.lock(), false);\n\n    if (!self)\n        return;\n\n    onFinishRender();\n}\n\nvoid CMonitorFrameScheduler::onPresented() {\n    if (!newSchedulingEnabled())\n        return;\n\n    if (!m_pendingThird)\n        return;\n\n    Log::logger->log(Log::TRACE, \"CMonitorFrameScheduler: {} -> onPresented, missed, committing pending.\", m_monitor->m_name);\n\n    m_pendingThird = false;\n\n    Log::logger->log(Log::TRACE, \"CMonitorFrameScheduler: {} -> onPresented, missed, committing pending at the earliest convenience.\", m_monitor->m_name);\n\n    m_pendingThird = false;\n\n    g_pEventLoopManager->doLater([m = m_monitor.lock()] {\n        if (!m)\n            return;\n        g_pHyprRenderer->commitPendingAndDoExplicitSync(m); // commit the pending frame. If it didn't fire yet (is not rendered) it doesn't matter. Syncs will wait.\n\n        // schedule a frame: we might have some missed damage, which got cleared due to the above commit.\n        // TODO: this is not always necessary, but doesn't hurt in general. We likely won't hit this if nothing's happening anyways.\n        if (m->m_damage.hasChanged())\n            g_pCompositor->scheduleFrameForMonitor(m);\n    });\n}\n\nvoid CMonitorFrameScheduler::onFrame() {\n    if (!canRender())\n        return;\n\n    m_monitor->recheckSolitary();\n\n    m_monitor->m_tearingState.busy = false;\n\n    if (m_monitor->m_tearingState.activelyTearing && m_monitor->m_solitaryClient.lock() /* can be invalidated by a recheck */) {\n\n        if (!m_monitor->m_tearingState.frameScheduledWhileBusy)\n            return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?\n\n        m_monitor->m_tearingState.nextRenderTorn          = true;\n        m_monitor->m_tearingState.frameScheduledWhileBusy = false;\n    }\n\n    if (!newSchedulingEnabled()) {\n        m_monitor->m_lastPresentationTimer.reset();\n\n        g_pHyprRenderer->renderMonitor(m_monitor.lock());\n        return;\n    }\n\n    if (!m_renderAtFrame) {\n        Log::logger->log(Log::TRACE, \"CMonitorFrameScheduler: {} -> frame event, but m_renderAtFrame = false.\", m_monitor->m_name);\n        return;\n    }\n\n    Log::logger->log(Log::TRACE, \"CMonitorFrameScheduler: {} -> frame event, render = true, rendering normally.\", m_monitor->m_name);\n\n    m_lastRenderBegun = hrc::now();\n\n    // get a ref to ourselves. renderMonitor can destroy this scheduler if it decides to perform a monitor reload\n    // FIXME: this is horrible. \"renderMonitor\" should not be able to do that.\n    auto self = m_self;\n\n    g_pHyprRenderer->renderMonitor(m_monitor.lock());\n\n    if (!self)\n        return;\n\n    onFinishRender();\n}\n\nvoid CMonitorFrameScheduler::onFinishRender() {\n    m_sync = CEGLSync::create(); // this destroys the old sync\n    g_pEventLoopManager->doOnReadable(m_sync->fd().duplicate(), [this, self = m_self] {\n        if (!self) // might've gotten destroyed\n            return;\n        onSyncFired();\n    });\n}\n\nbool CMonitorFrameScheduler::canRender() {\n    if ((g_pCompositor->m_aqBackend->hasSession() && !g_pCompositor->m_aqBackend->session->active) || !g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState) {\n        Log::logger->log(Log::WARN, \"Attempted to render frame on inactive session!\");\n\n        if (g_pCompositor->m_unsafeState && std::ranges::any_of(g_pCompositor->m_monitors.begin(), g_pCompositor->m_monitors.end(), [&](auto& m) {\n                return m->m_output != g_pCompositor->m_unsafeOutput->m_output;\n            })) {\n            // restore from unsafe state\n            g_pCompositor->leaveUnsafeState();\n        }\n\n        return false; // cannot draw on session inactive (different tty)\n    }\n\n    if (!m_monitor->m_enabled)\n        return false;\n\n    return true;\n}\n"
  },
  {
    "path": "src/helpers/MonitorFrameScheduler.hpp",
    "content": "#pragma once\n\n#include \"Monitor.hpp\"\n\n#include <chrono>\n\nclass CEGLSync;\n\nclass CMonitorFrameScheduler {\n  public:\n    using hrc = std::chrono::high_resolution_clock;\n\n    CMonitorFrameScheduler(PHLMONITOR m);\n\n    CMonitorFrameScheduler(const CMonitorFrameScheduler&)            = delete;\n    CMonitorFrameScheduler(CMonitorFrameScheduler&&)                 = delete;\n    CMonitorFrameScheduler& operator=(const CMonitorFrameScheduler&) = delete;\n    CMonitorFrameScheduler& operator=(CMonitorFrameScheduler&&)      = delete;\n\n    void                    onSyncFired();\n    void                    onPresented();\n    void                    onFrame();\n\n  private:\n    bool                       canRender();\n    void                       onFinishRender();\n    bool                       newSchedulingEnabled();\n\n    bool                       m_renderAtFrame = true;\n    bool                       m_pendingThird  = false;\n    hrc::time_point            m_lastRenderBegun;\n\n    PHLMONITORREF              m_monitor;\n\n    UP<CEGLSync>               m_sync;\n\n    WP<CMonitorFrameScheduler> m_self;\n\n    friend class CMonitor;\n};\n"
  },
  {
    "path": "src/helpers/MonitorZoomController.cpp",
    "content": "#include \"MonitorZoomController.hpp\"\n\n#include <hyprlang.hpp>\n#include \"../config/ConfigValue.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../render/OpenGL.hpp\"\n#include \"desktop/DesktopTypes.hpp\"\n#include \"render/Renderer.hpp\"\n\nvoid CMonitorZoomController::zoomWithDetachedCamera(CBox& result, const SRenderData& m_renderData) {\n    const auto m      = m_renderData.pMonitor;\n    auto       monbox = CBox(0, 0, m->m_size.x, m->m_size.y);\n    const auto ZOOM   = g_pHyprRenderer->m_renderData.mouseZoomFactor;\n    const auto MOUSE  = g_pInputManager->getMouseCoordsInternal() - m->m_position;\n\n    if (m_lastZoomLevel != ZOOM) {\n        if (m_resetCameraState) {\n            m_resetCameraState = false;\n            m_camera           = CBox(0, 0, m->m_size.x, m->m_size.y);\n            m_lastZoomLevel    = 1.0f;\n        }\n        const CBox old = m_camera;\n\n        // mouse normalized inside screen (0..1)\n        const float mx = MOUSE.x / m->m_size.x;\n        const float my = MOUSE.y / m->m_size.y;\n        // world-space point under the cursor before zoom\n        const float mouseWorldX = old.x + (mx * old.w);\n        const float mouseWorldY = old.y + (my * old.h);\n\n        const auto  CAMERAW = monbox.w / ZOOM;\n        const auto  CAMERAH = monbox.h / ZOOM;\n\n        // compute new top-left so the same world point stays under the cursor\n        const float newX = mouseWorldX - (mx * CAMERAW);\n        const float newY = mouseWorldY - (my * CAMERAH);\n\n        m_camera = CBox(newX, newY, CAMERAW, CAMERAH);\n        // Detect if this zoom would've caused jerk to keep mouse in view and disable edges if so\n        if (!m_camera.copy().scaleFromCenter(.9).containsPoint(MOUSE))\n            m_padCamEdges = false;\n        m_lastZoomLevel = ZOOM;\n    }\n\n    // Keep mouse inside cameraview\n    auto smallerbox = m_camera;\n    // Prevent zoom step from causing us to jerk to keep mouse in padded camera view,\n    // but let us switch to the padded camera once the mouse moves into the safe area\n    if (!m_padCamEdges)\n        if (smallerbox.copy().scaleFromCenter(.9).containsPoint(MOUSE))\n            m_padCamEdges = true;\n    if (m_padCamEdges)\n        smallerbox.scaleFromCenter(.9);\n    if (!smallerbox.containsPoint(MOUSE)) {\n        if (MOUSE.x < smallerbox.x)\n            m_camera.x -= smallerbox.x - MOUSE.x;\n        if (MOUSE.y < smallerbox.y)\n            m_camera.y -= smallerbox.y - MOUSE.y;\n        if (MOUSE.y > smallerbox.y + smallerbox.h)\n            m_camera.y += MOUSE.y - (smallerbox.y + smallerbox.h);\n        if (MOUSE.x > smallerbox.x + smallerbox.w)\n            m_camera.x += MOUSE.x - (smallerbox.x + smallerbox.w);\n    }\n\n    auto z = ZOOM * m->m_scale;\n    monbox.scale(z).translate(-m_camera.pos() * z);\n\n    result = monbox;\n}\n\nvoid CMonitorZoomController::applyZoomTransform(CBox& monbox, const SRenderData& m_renderData) {\n    static auto PZOOMRIGID          = CConfigValue<Hyprlang::INT>(\"cursor:zoom_rigid\");\n    static auto PZOOMDETACHEDCAMERA = CConfigValue<Hyprlang::INT>(\"cursor:zoom_detached_camera\");\n    const auto  ZOOM                = g_pHyprRenderer->m_renderData.mouseZoomFactor;\n\n    if (ZOOM == 1.0f)\n        return;\n\n    const auto m        = m_renderData.pMonitor;\n    const auto ORIGINAL = monbox;\n    const auto INITANIM = m->m_zoomAnimProgress->value() != 1.0;\n\n    if (*PZOOMDETACHEDCAMERA && !INITANIM)\n        zoomWithDetachedCamera(monbox, m_renderData);\n    else {\n        const auto ZOOMCENTER =\n            g_pHyprRenderer->m_renderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m->m_position) * m->m_scale : m->m_transformedSize / 2.f;\n\n        monbox.translate(-ZOOMCENTER).scale(ZOOM).translate(*PZOOMRIGID ? m->m_transformedSize / 2.0 : ZOOMCENTER);\n    }\n\n    monbox.x = std::min(monbox.x, 0.0);\n    monbox.y = std::min(monbox.y, 0.0);\n    if (monbox.x + monbox.width < ORIGINAL.w)\n        monbox.x = ORIGINAL.w - monbox.width;\n    if (monbox.y + monbox.height < ORIGINAL.h)\n        monbox.y = ORIGINAL.h - monbox.height;\n}\n"
  },
  {
    "path": "src/helpers/MonitorZoomController.hpp",
    "content": "#pragma once\n\n#include \"./math/Math.hpp\"\n\nstruct SRenderData;\n\nclass CMonitorZoomController {\n  public:\n    bool m_resetCameraState = true;\n\n    void applyZoomTransform(CBox& monbox, const SRenderData& m_renderData);\n\n  private:\n    void  zoomWithDetachedCamera(CBox& result, const SRenderData& m_renderData);\n\n    CBox  m_camera;\n    float m_lastZoomLevel = 1.0f;\n    bool  m_padCamEdges   = true;\n};\n"
  },
  {
    "path": "src/helpers/SdDaemon.cpp",
    "content": "#include \"SdDaemon.hpp\"\n#include \"memory/Memory.hpp\"\n\n#include <memory>\n#include <fcntl.h>\n#include <unistd.h>\n#include <cerrno>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <cstdlib>\n#include <cstring>\n\nint NSystemd::sdBooted() {\n    if (!faccessat(AT_FDCWD, \"/run/systemd/system/\", F_OK, AT_SYMLINK_NOFOLLOW))\n        return true;\n\n    if (errno == ENOENT)\n        return false;\n\n    return -errno;\n}\n\nint NSystemd::sdNotify(int unsetEnvironment, const char* state) {\n    int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);\n    if (fd < 0)\n        return -errno;\n\n    constexpr char envVar[] = \"NOTIFY_SOCKET\";\n\n    auto           cleanup = [unsetEnvironment, envVar](const int* fd) {\n        if (unsetEnvironment)\n            unsetenv(envVar);\n        close(*fd);\n    };\n    std::unique_ptr<int, decltype(cleanup)> fdCleaup(&fd, cleanup);\n\n    const char*                             addr = getenv(envVar);\n    if (!addr)\n        return 0;\n\n    struct sockaddr_un unixAddr = {0};\n\n    size_t             addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1);\n\n    unixAddr.sun_family = AF_UNIX;\n    strncpy(unixAddr.sun_path, addr, addrLen);\n    if (unixAddr.sun_path[0] == '@')\n        unixAddr.sun_path[0] = '\\0';\n\n    if (connect(fd, rc<const sockaddr*>(&unixAddr), sizeof(struct sockaddr_un)) < 0)\n        return -errno;\n\n    // arbitrary value which seems to be enough for s-d messages\n    ssize_t stateLen = strnlen(state, 128);\n    if (write(fd, state, stateLen) == stateLen)\n        return 1;\n\n    return -errno;\n}\n"
  },
  {
    "path": "src/helpers/SdDaemon.hpp",
    "content": "#pragma once\n\nnamespace NSystemd {\n    int sdBooted(void);\n    int sdNotify(int unset_environment, const char* state);\n}\n"
  },
  {
    "path": "src/helpers/Splashes.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <string>\n\nnamespace NSplashes {\n    inline const std::vector<std::string> SPLASHES = {\n        // clang-format off\n        \"Woo, animations!\",\n        \"It's like Hypr, but better.\",\n        \"Release 1.0 when?\",\n        \"It's not awesome, it's Hyprland!\",\n        \"\\\"I commit too often, people can't catch up lmao\\\" - Vaxry\",\n        \"This text is random.\",\n        \"\\\"There are reasons to not use rust.\\\" - Boga\",\n        \"Read the wiki.\",\n        \"\\\"Hello everyone this is YOUR daily dose of ‘read the wiki’\\\" - Vaxry\",\n        \"h\",\n        \"\\\"‘why no work’, bro I haven't hacked your pc to get live feeds yet\\\" - Vaxry\",\n        \"Compile, wait for 20 minutes, notice a new commit, compile again.\",\n        \"To rice, or not to rice, that is the question.\",\n        \"Now available on Fedora!\",\n        \"\\\"Hyprland is so good it starts with a capital letter\\\" - Hazel\",\n        \"\\\"please make this message a splash\\\" - eriedaberrie\",\n        \"\\\"the only wayland compositor powered by fried chicken\\\" - raf\",\n        \"\\\"This will never get into Hyprland\\\" - Flafy\",\n        \"\\\"Hyprland only gives you up on -git\\\" - fazzi\",\n        \"Segmentation fault (core dumped)\",\n        \"\\\"disabling hyprland logo is a war crime\\\" - vaxry\",\n        \"some basic startup code\",\n        \"\\\"I think I am addicted to hyprland\\\" - mathisbuilder\",\n        \"\\\"hyprland is the most important package in the arch repos\\\" - jacekpoz\",\n        \"Thanks Brodie!\",\n        \"Thanks fufexan!\",\n        \"Thanks raf!\",\n        \"You can't use --splash to change this message :)\",\n        \"Hyprland will overtake Gnome in popularity by [insert year]\",\n        \"Designed in California - Assembled in China\",\n        \"\\\"something <time here> and still no new splash\\\" - snowman\",\n        \"My name is Land. Hypr Land. One red bull, shaken not stirred.\",\n        \"\\\"Glory To The Emperor\\\" - raf\",\n        \"Help I forgot to install kitty\",\n        \"Go to settings to activate Hyprland\",\n        \"Why is there code??? Make a damn .exe file and give it to me.\",\n        \"Hyprland is not a window manager!\",\n        \"Can we get a version without anime girls?\",\n        \"Check out quickshell!\",\n        \"A day without Hyprland is a day wasted\",\n        \"By dt, do you mean damage tracking or distrotube?\",\n        \"Made in Poland\",\n        \"\\\"I use Arch, btw\\\" - John Cena\",\n        R\"(\"Hyper\".replace(\"e\", \"\"))\",\n        \"\\\"my win11 install runs hyprland that is true\\\" - raf\",\n        \"\\\"stop playing league loser\\\" - hyprBot\",\n        \"\\\"If it ain't broke, don't fix it\\\" - Lucascito_03\",\n        \"\\\"@vaxry how do i learn c++\\\" - flicko\",\n        \"Join the discord server!\",\n        \"Thanks ThatOneCalculator!\",\n        \"The AUR packages always work, except for the times they don't.\",\n        \"Funny animation compositor woo\",\n        \"3 years!\",\n        \"Beauty will save the world\", // 4th ricing comp winner - zacoons' choice\n        // music reference / quote section\n        \"J'remue le ciel, le jour, la nuit.\",\n        \"aezakmi, aezakmi, aezakmi, aezakmi, aezakmi, aezakmi, aezakmi!\",\n        \"Wir sind schon sehr lang zusammen...\",\n        \"I see a red door and I want it painted black.\",\n        \"Take on me, take me on...\",\n        \"You spin me right round baby right round\",\n        \"Stayin' alive, stayin' alive\",\n        \"Say no way, say no way ya, no way!\",\n        \"Ground control to Major Tom...\",\n        \"Alors on danse\",\n        \"And all that I can see, is just a yellow lemon tree.\",\n        \"Got a one-way ticket to the blues\",\n        \"Is this the real life, is this just fantasy\",\n        \"What's in your head, in your head?\",\n        \"We're all living in America, America, America.\",\n        \"I'm still standing, better than I ever did\",\n        \"Here comes the sun, bringing you love and shining on everyone\",\n        \"Two trailer park girls go round the outside\",\n        \"With the lights out, it's less dangerous\",\n        \"Here we go back, this is the moment, tonight is the night\",\n        \"Now you're just somebody that I used to know...\",\n        \"Black bird, black moon, black sky\",\n        \"Some legends are told, some turn to dust or to gold\",\n        \"Your brain gets smart, but your head gets dumb.\",\n        \"Save your mercy for someone who needs it more\",\n        \"You're gonna hear my voice when I shout it out loud\",\n        \"Ding ding pch n daa, bam-ba-ba-re-bam baram bom bom baba-bam-bam-bommm\",\n        \"Súbeme la radio que esta es mi canción\",\n        \"I'm beggin', beggin' you\",\n        \"Never gonna let you down (I am trying!)\",\n        \"Hier kommt die Sonne\",\n        \"Kickstart my heart, give it a start\",\n        \"Fear of the dark, I have a constant fear that something's always near\",\n        \"Komm mit, reih dich ein.\",\n        \"I wish I had an angel for one moment of love\",\n        \"We're the children of the dark\",\n        \"You float like a feather, in a beautiful world\",\n        \"Demons come at night and they bring the end\",\n        \"All I wanna say is that they don't really care about us\",\n        \"Has he lost his mind? Can he see or is he blind?\",\n        // clang-format on\n    };\n\n    inline const std::vector<std::string> SPLASHES_CHRISTMAS = {\n        // clang-format off\n        \"Merry Christmas!\",\n        \"Merry Xmas!\",\n        \"Ho ho ho\",\n        \"Santa was here\",\n        \"Make sure to spend some jolly time with those near and dear to you!\",\n        \"Have you checked for christmas presents yet?\",\n        // clang-format on\n    };\n\n    // ONLY valid near new years.\n    inline static int newYear = []() -> int {\n        auto tt    = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n        auto local = *localtime(&tt);\n\n        if (local.tm_mon < 8 /* decided with a fair die I promise. */)\n            return local.tm_year + 1900;\n        return local.tm_year + 1901;\n    }();\n\n    inline const std::vector<std::string> SPLASHES_NEWYEAR = {\n        // clang-format off\n        \"Happy new Year!\",\n        \"[New year] will be the year of the Linux desktop!\",\n        \"[New year] will be the year of the Hyprland desktop!\",\n        std::format(\"{} will be the year of the Linux desktop!\", newYear),\n        std::format(\"{} will be the year of the Hyprland desktop!\", newYear),\n        std::format(\"Let's make {} even better than {}!\", newYear, newYear - 1),\n        // clang-format on\n    };\n};"
  },
  {
    "path": "src/helpers/TagKeeper.cpp",
    "content": "#include \"TagKeeper.hpp\"\n\nbool CTagKeeper::isTagged(const std::string& tag, bool strict) const {\n    const bool NEGATIVE = tag.starts_with(\"negative\");\n    const auto MATCH    = NEGATIVE ? tag.substr(9) : tag;\n    const bool TAGGED   = m_tags.contains(MATCH) || (!strict && m_tags.contains(MATCH + \"*\"));\n    return NEGATIVE ? !TAGGED : TAGGED;\n}\n\nbool CTagKeeper::applyTag(const std::string& tag, bool dynamic) {\n\n    std::string tagReal = tag;\n\n    if (dynamic && !tag.ends_with(\"*\"))\n        tagReal += \"*\";\n\n    bool changed = true;\n    bool setTag  = true;\n\n    if (tagReal.starts_with(\"-\")) { // unset\n        tagReal = tagReal.substr(1);\n        changed = isTagged(tagReal, true);\n        setTag  = false;\n    } else if (tagReal.starts_with(\"+\")) { // set\n        tagReal = tagReal.substr(1);\n        changed = !isTagged(tagReal, true);\n    } else // toggle if without prefix\n        setTag = !isTagged(tagReal, true);\n\n    if (!changed)\n        return false;\n\n    if (setTag)\n        m_tags.emplace(tagReal);\n    else\n        m_tags.erase(tagReal);\n\n    return true;\n}\n\nbool CTagKeeper::removeDynamicTag(const std::string& s) {\n    return std::erase_if(m_tags, [&s](const auto& tag) { return tag == s + \"*\"; });\n}\n"
  },
  {
    "path": "src/helpers/TagKeeper.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <set>\n\nclass CTagKeeper {\n  public:\n    bool        isTagged(const std::string& tag, bool strict = false) const;\n    bool        applyTag(const std::string& tag, bool dynamic = false);\n    bool        removeDynamicTag(const std::string& tag);\n\n    const auto& getTags() const {\n        return m_tags;\n    };\n\n  private:\n    std::set<std::string> m_tags;\n};\n"
  },
  {
    "path": "src/helpers/TransferFunction.cpp",
    "content": "#include \"TransferFunction.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../event/EventBus.hpp\"\n#include <string>\n#include <unordered_map>\n#include <hyprlang.hpp>\n\nusing namespace NTransferFunction;\n\nstatic std::unordered_map<std::string, eTF> const table = {{\"default\", TF_DEFAULT}, {\"0\", TF_DEFAULT},       {\"auto\", TF_AUTO}, {\"srgb\", TF_SRGB},\n                                                           {\"3\", TF_SRGB},          {\"gamma22\", TF_GAMMA22}, {\"1\", TF_GAMMA22}, {\"gamma22force\", TF_FORCED_GAMMA22},\n                                                           {\"2\", TF_FORCED_GAMMA22}};\n\neTF                                               NTransferFunction::fromString(const std::string tfName) {\n    auto it = table.find(tfName);\n    if (it == table.end())\n        return TF_DEFAULT;\n    return it->second;\n}\n\nstd::string NTransferFunction::toString(eTF tf) {\n    for (const auto& [key, value] : table) {\n        if (value == tf)\n            return key;\n    }\n    return \"\";\n}\n\neTF NTransferFunction::fromConfig(bool useICC) {\n    if (useICC)\n        return TF_SRGB;\n\n    static auto PSDREOTF = CConfigValue<Hyprlang::STRING>(\"render:cm_sdr_eotf\");\n    static auto sdrEOTF  = NTransferFunction::fromString(*PSDREOTF);\n    static auto P        = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); });\n\n    return sdrEOTF;\n}\n"
  },
  {
    "path": "src/helpers/TransferFunction.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <string>\n\nnamespace NTransferFunction {\n    enum eTF : uint8_t {\n        TF_DEFAULT        = 0,\n        TF_AUTO           = 1,\n        TF_SRGB           = 2,\n        TF_GAMMA22        = 3,\n        TF_FORCED_GAMMA22 = 4,\n    };\n\n    eTF         fromString(const std::string tfName);\n    std::string toString(eTF tf);\n\n    eTF         fromConfig(bool useICC = false);\n}\n"
  },
  {
    "path": "src/helpers/WLClasses.cpp",
    "content": "#include \"WLClasses.hpp\"\n"
  },
  {
    "path": "src/helpers/WLClasses.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../desktop/view/Subsurface.hpp\"\n#include \"../desktop/view/Popup.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../macros.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"memory/Memory.hpp\"\n#include \"signal/Signal.hpp\"\n\nclass CMonitor;\nclass IPointer;\nclass IKeyboard;\nclass CWLSurfaceResource;\n\nAQUAMARINE_FORWARD(ISwitch);\n\nstruct SSwitchDevice {\n    WP<Aquamarine::ISwitch> pDevice;\n\n    struct {\n        CHyprSignalListener destroy;\n        CHyprSignalListener fire;\n    } listeners;\n\n    bool operator==(const SSwitchDevice& other) const {\n        return pDevice == other.pDevice;\n    }\n};\n"
  },
  {
    "path": "src/helpers/cm/ColorManagement.cpp",
    "content": "#include \"ColorManagement.hpp\"\n#include \"../../macros.hpp\"\n#include <hyprutils/memory/UniquePtr.hpp>\n#include <map>\n#include <vector>\n\nusing namespace NColorManagement;\n\nnamespace NColorManagement {\n    // expected to be small\n    static std::vector<UP<const CPrimaries>>                       knownPrimaries;\n    static std::vector<UP<const CImageDescription>>                knownDescriptions;\n    static std::map<std::pair<uint, uint>, Hyprgraphics::CMatrix3> primariesConversion;\n}\n\nconst SPCPRimaries& NColorManagement::getPrimaries(ePrimaries name) {\n    switch (name) {\n        case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709;\n        case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020;\n        case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M;\n        case CM_PRIMARIES_PAL: return NColorPrimaries::PAL;\n        case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC;\n        case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM;\n        case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ;\n        case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3;\n        case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3;\n        case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB;\n        default: return NColorPrimaries::DEFAULT_PRIMARIES;\n    }\n}\n\nCPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint32_t primariesId) : m_id(primariesId), m_primaries(primaries) {\n    m_primaries2XYZ = m_primaries.toXYZ();\n}\n\nWP<const CPrimaries> CPrimaries::from(const SPCPRimaries& primaries) {\n    for (const auto& known : knownPrimaries) {\n        if (known->value() == primaries)\n            return known;\n    }\n\n    knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1)));\n    return knownPrimaries.back();\n}\n\nWP<const CPrimaries> CPrimaries::from(const ePrimaries name) {\n    return from(getPrimaries(name));\n}\n\nWP<const CPrimaries> CPrimaries::from(const uint32_t primariesId) {\n    ASSERT(primariesId <= knownPrimaries.size());\n    return knownPrimaries[primariesId - 1];\n}\n\nconst SPCPRimaries& CPrimaries::value() const {\n    return m_primaries;\n}\n\nuint CPrimaries::id() const {\n    return m_id;\n}\n\nconst Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const {\n    return m_primaries2XYZ;\n}\n\nconst Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP<const CPrimaries> dst) const {\n    const auto cacheKey = std::make_pair(m_id, dst->m_id);\n    if (!primariesConversion.contains(cacheKey))\n        primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries)));\n\n    return primariesConversion[cacheKey];\n}\n\nCImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint32_t imageDescriptionId) :\n    m_id(imageDescriptionId), m_imageDescription(imageDescription) {\n    m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id();\n}\n\nPImageDescription CImageDescription::from(const SImageDescription& imageDescription) {\n    for (const auto& known : knownDescriptions) {\n        if (known->value() == imageDescription)\n            return known;\n    }\n\n    knownDescriptions.emplace_back(UP<CImageDescription>(new CImageDescription(imageDescription, knownDescriptions.size() + 1)));\n    return knownDescriptions.back();\n}\n\nPImageDescription CImageDescription::from(const uint32_t imageDescriptionId) {\n    ASSERT(imageDescriptionId <= knownDescriptions.size());\n    return knownDescriptions[imageDescriptionId - 1];\n}\n\nPImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const {\n    auto desc       = m_imageDescription;\n    desc.luminances = luminances;\n    return CImageDescription::from(desc);\n}\n\nconst SImageDescription& CImageDescription::value() const {\n    return m_imageDescription;\n}\n\nuint CImageDescription::id() const {\n    return m_id;\n}\n\nWP<const CPrimaries> CImageDescription::getPrimaries() const {\n    return CPrimaries::from(m_primariesId);\n}\n\nstatic Mat3x3 diag3(const std::array<float, 3>& s) {\n    return Mat3x3{std::array<float, 9>{s[0], 0, 0, 0, s[1], 0, 0, 0, s[2]}};\n}\n\nstatic std::optional<Mat3x3> invertMat3(const Mat3x3& m) {\n    const auto   ARR = m.getMatrix();\n    const double a = ARR[0], b = ARR[1], c = ARR[2];\n    const double d = ARR[3], e = ARR[4], f = ARR[5];\n    const double g = ARR[6], h = ARR[7], i = ARR[8];\n\n    const double A = (e * i - f * h);\n    const double B = -(d * i - f * g);\n    const double C = (d * h - e * g);\n    const double D = -(b * i - c * h);\n    const double E = (a * i - c * g);\n    const double F = -(a * h - b * g);\n    const double G = (b * f - c * e);\n    const double H = -(a * f - c * d);\n    const double I = (a * e - b * d);\n\n    const double det = a * A + b * B + c * C;\n    if (std::abs(det) < 1e-18)\n        return std::nullopt;\n\n    const double invDet = 1.0 / det;\n    Mat3x3       inv{std::array<float, 9>{\n        A * invDet,\n        D * invDet,\n        G * invDet, //\n        B * invDet,\n        E * invDet,\n        H * invDet, //\n        C * invDet,\n        F * invDet,\n        I * invDet, //\n    }};\n    return inv;\n}\n\nstatic std::array<float, 3> matByVec(const Mat3x3& M, const std::array<float, 3>& v) {\n    const auto ARR = M.getMatrix();\n    return {ARR[0] * v[0] + ARR[1] * v[1] + ARR[2] * v[2], ARR[3] * v[0] + ARR[4] * v[1] + ARR[5] * v[2], ARR[6] * v[0] + ARR[7] * v[1] + ARR[8] * v[2]};\n}\n\nstd::optional<Mat3x3> NColorManagement::rgbToXYZFromPrimaries(SPCPRimaries pr) {\n    const auto R = Hyprgraphics::xy2xyz(pr.red);\n    const auto G = Hyprgraphics::xy2xyz(pr.green);\n    const auto B = Hyprgraphics::xy2xyz(pr.blue);\n    const auto W = Hyprgraphics::xy2xyz(pr.white);\n\n    // P has columns R,G,B\n    Mat3x3 P{std::array<float, 9>{R.x, G.x, B.x, R.y, G.y, B.y, R.z, G.z, B.z}};\n\n    auto   invP = invertMat3(P);\n    if (!invP)\n        return std::nullopt;\n\n    const auto S = matByVec(*invP, {W.x, W.y, W.z});\n\n    P.multiply(diag3(S)); // RGB->XYZ\n\n    return P;\n}\n\nMat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW) {\n    static const Mat3x3        Bradford{std::array<float, 9>{0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f}};\n    static const Mat3x3        BradfordInv = invertMat3(Bradford).value();\n\n    const auto                 srcXYZ = Hyprgraphics::xy2xyz(srcW);\n    const auto                 dstXYZ = Hyprgraphics::xy2xyz(dstW);\n\n    const auto                 srcLMS = matByVec(Bradford, {srcXYZ.x, srcXYZ.y, srcXYZ.z});\n    const auto                 dstLMS = matByVec(Bradford, {dstXYZ.x, dstXYZ.y, dstXYZ.z});\n\n    const std::array<float, 3> scale{dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2]};\n\n    Mat3x3                     result = BradfordInv;\n    result.multiply(diag3(scale)).multiply(Bradford);\n\n    return result;\n}"
  },
  {
    "path": "src/helpers/cm/ColorManagement.hpp",
    "content": "#pragma once\n\n#include \"color-management-v1.hpp\"\n#include <hyprgraphics/color/Color.hpp>\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n\n#include <filesystem>\n#include <vector>\n#include <expected>\n\n#define SDR_MIN_LUMINANCE 0.2\n#define SDR_MAX_LUMINANCE 80.0\n#define SDR_REF_LUMINANCE 80.0\n#define HDR_MIN_LUMINANCE 0.005\n#define HDR_MAX_LUMINANCE 10000.0\n#define HDR_REF_LUMINANCE 203.0\n#define HLG_MAX_LUMINANCE 1000.0\n\nclass ITexture;\n\nnamespace NColorManagement {\n    enum eNoShader : uint8_t {\n        CM_NS_DISABLE  = 0,\n        CM_NS_ALWAYS   = 1,\n        CM_NS_ONDEMAND = 2,\n        CM_NS_IGNORE   = 3,\n    };\n\n    enum ePrimaries : uint8_t {\n        CM_PRIMARIES_SRGB         = 1,\n        CM_PRIMARIES_PAL_M        = 2,\n        CM_PRIMARIES_PAL          = 3,\n        CM_PRIMARIES_NTSC         = 4,\n        CM_PRIMARIES_GENERIC_FILM = 5,\n        CM_PRIMARIES_BT2020       = 6,\n        CM_PRIMARIES_CIE1931_XYZ  = 7,\n        CM_PRIMARIES_DCI_P3       = 8,\n        CM_PRIMARIES_DISPLAY_P3   = 9,\n        CM_PRIMARIES_ADOBE_RGB    = 10,\n    };\n\n    enum eTransferFunction : uint8_t {\n        CM_TRANSFER_FUNCTION_BT1886     = 1,\n        CM_TRANSFER_FUNCTION_GAMMA22    = 2,\n        CM_TRANSFER_FUNCTION_GAMMA28    = 3,\n        CM_TRANSFER_FUNCTION_ST240      = 4,\n        CM_TRANSFER_FUNCTION_EXT_LINEAR = 5,\n        CM_TRANSFER_FUNCTION_LOG_100    = 6,\n        CM_TRANSFER_FUNCTION_LOG_316    = 7,\n        CM_TRANSFER_FUNCTION_XVYCC      = 8,\n        CM_TRANSFER_FUNCTION_SRGB       = 9,\n        CM_TRANSFER_FUNCTION_EXT_SRGB   = 10,\n        CM_TRANSFER_FUNCTION_ST2084_PQ  = 11,\n        CM_TRANSFER_FUNCTION_ST428      = 12,\n        CM_TRANSFER_FUNCTION_HLG        = 13,\n    };\n\n    // NOTE should be ok this way. unsupported primaries/tfs must be rejected earlier. supported enum values should be in sync with proto.\n    // might need a proper switch-case and additional INVALID enum value.\n    inline wpColorManagerV1Primaries convertPrimaries(ePrimaries primaries) {\n        return sc<wpColorManagerV1Primaries>(primaries);\n    }\n    inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) {\n        return sc<ePrimaries>(primaries);\n    }\n    inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf) {\n        return sc<wpColorManagerV1TransferFunction>(tf);\n    }\n    inline eTransferFunction convertTransferFunction(wpColorManagerV1TransferFunction tf) {\n        return sc<eTransferFunction>(tf);\n    }\n\n    using SPCPRimaries = Hyprgraphics::SPCPRimaries;\n\n    namespace NColorPrimaries {\n\n        static const auto BT709 = SPCPRimaries{\n            .red   = {.x = 0.64, .y = 0.33},\n            .green = {.x = 0.30, .y = 0.60},\n            .blue  = {.x = 0.15, .y = 0.06},\n            .white = {.x = 0.3127, .y = 0.3290},\n        };\n\n        static const auto DEFAULT_PRIMARIES = BT709;\n\n        static const auto PAL_M = SPCPRimaries{\n            .red   = {.x = 0.67, .y = 0.33},\n            .green = {.x = 0.21, .y = 0.71},\n            .blue  = {.x = 0.14, .y = 0.08},\n            .white = {.x = 0.310, .y = 0.316},\n        };\n\n        static const auto PAL = SPCPRimaries{\n            .red   = {.x = 0.640, .y = 0.330},\n            .green = {.x = 0.290, .y = 0.600},\n            .blue  = {.x = 0.150, .y = 0.060},\n            .white = {.x = 0.3127, .y = 0.3290},\n        };\n\n        static const auto NTSC = SPCPRimaries{\n            .red   = {.x = 0.630, .y = 0.340},\n            .green = {.x = 0.310, .y = 0.595},\n            .blue  = {.x = 0.155, .y = 0.070},\n            .white = {.x = 0.3127, .y = 0.3290},\n        };\n\n        static const auto GENERIC_FILM = SPCPRimaries{\n            .red   = {.x = 0.243, .y = 0.692},\n            .green = {.x = 0.145, .y = 0.049},\n            .blue  = {.x = 0.681, .y = 0.319}, // NOLINT(modernize-use-std-numbers)\n            .white = {.x = 0.310, .y = 0.316},\n        };\n\n        static const auto BT2020 = SPCPRimaries{\n            .red   = {.x = 0.708, .y = 0.292},\n            .green = {.x = 0.170, .y = 0.797},\n            .blue  = {.x = 0.131, .y = 0.046},\n            .white = {.x = 0.3127, .y = 0.3290},\n        };\n\n        static const auto CIE1931_XYZ = SPCPRimaries{\n            .red   = {.x = 1.0, .y = 0.0},\n            .green = {.x = 0.0, .y = 1.0},\n            .blue  = {.x = 0.0, .y = 0.0},\n            .white = {.x = 1.0 / 3.0, .y = 1.0 / 3.0},\n        };\n\n        static const auto DCI_P3 = SPCPRimaries{\n            .red   = {.x = 0.680, .y = 0.320},\n            .green = {.x = 0.265, .y = 0.690},\n            .blue  = {.x = 0.150, .y = 0.060},\n            .white = {.x = 0.314, .y = 0.351},\n        };\n\n        static const auto DISPLAY_P3 = SPCPRimaries{\n            .red   = {.x = 0.680, .y = 0.320},\n            .green = {.x = 0.265, .y = 0.690},\n            .blue  = {.x = 0.150, .y = 0.060},\n            .white = {.x = 0.3127, .y = 0.3290},\n        };\n\n        static const auto ADOBE_RGB = SPCPRimaries{\n            .red   = {.x = 0.6400, .y = 0.3300},\n            .green = {.x = 0.2100, .y = 0.7100},\n            .blue  = {.x = 0.1500, .y = 0.0600},\n            .white = {.x = 0.3127, .y = 0.3290},\n        };\n    }\n\n    struct SVCGTTable16 {\n        uint16_t                             channels  = 0;\n        uint16_t                             entries   = 0;\n        uint16_t                             entrySize = 0;\n        std::array<std::vector<uint16_t>, 3> ch;\n    };\n\n    const SPCPRimaries&   getPrimaries(ePrimaries name);\n    std::optional<Mat3x3> rgbToXYZFromPrimaries(SPCPRimaries pr);\n    Mat3x3                adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW);\n\n    class CPrimaries {\n      public:\n        static WP<const CPrimaries>   from(const SPCPRimaries& primaries);\n        static WP<const CPrimaries>   from(const ePrimaries name);\n        static WP<const CPrimaries>   from(const uint primariesId);\n\n        const SPCPRimaries&           value() const;\n        uint                          id() const;\n\n        const Hyprgraphics::CMatrix3& toXYZ() const;                                       // toXYZ() * rgb -> xyz\n        const Hyprgraphics::CMatrix3& convertMatrix(const WP<const CPrimaries> dst) const; // convertMatrix(dst) * rgb with \"this\" primaries -> rgb with dst primaries\n\n      private:\n        CPrimaries(const SPCPRimaries& primaries, const uint primariesId);\n        uint                   m_id;\n        SPCPRimaries           m_primaries;\n\n        Hyprgraphics::CMatrix3 m_primaries2XYZ;\n    };\n\n    struct SImageDescription {\n        static std::expected<SImageDescription, std::string> fromICC(const std::filesystem::path& file);\n\n        //\n        std::vector<uint8_t> rawICC;\n\n        eTransferFunction    transferFunction      = CM_TRANSFER_FUNCTION_GAMMA22;\n        float                transferFunctionPower = 1.0f;\n        bool                 windowsScRGB          = false;\n\n        bool                 primariesNameSet = false;\n        ePrimaries           primariesNamed   = CM_PRIMARIES_SRGB;\n        // primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0)\n        // wayland protocol expects int32_t values multiplied by 1000000\n        // drm expects uint16_t values multiplied by 50000\n        SPCPRimaries primaries, masteringPrimaries;\n\n        // luminances in cd/m²\n        // protos and drm expect min * 10000\n        struct SPCLuminances {\n            float    min       = 0.2; // 0.2 cd/m²\n            uint32_t max       = 80;  // 80 cd/m²\n            uint32_t reference = 80;  // 80 cd/m²\n            bool     operator==(const SPCLuminances& l2) const {\n                return min == l2.min && max == l2.max && reference == l2.reference;\n            }\n        } luminances;\n        struct SPCMasteringLuminances {\n            float    min = 0;\n            uint32_t max = 0;\n            bool     operator==(const SPCMasteringLuminances& l2) const {\n                return min == l2.min && max == l2.max;\n            }\n        } masteringLuminances;\n\n        // Matrix data from ICC\n        struct SICCData {\n            bool                        present = false;\n            size_t                      lutSize = 33;\n            std::vector<float>          lutDataPacked;\n            SP<ITexture>                lutTexture;\n            std::optional<SVCGTTable16> vcgt;\n        } icc;\n\n        uint32_t maxCLL  = 0;\n        uint32_t maxFALL = 0;\n\n        bool     operator==(const SImageDescription& d2) const {\n            if (icc.present || d2.icc.present)\n                return false;\n\n            return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower &&\n                (primariesNameSet == d2.primariesNameSet && (primariesNameSet ? primariesNamed == d2.primariesNamed : primaries == d2.primaries)) &&\n                masteringPrimaries == d2.masteringPrimaries && luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL &&\n                maxFALL == d2.maxFALL;\n        }\n\n        const SPCPRimaries& getPrimaries() const {\n            if (primariesNameSet || primaries == SPCPRimaries{})\n                return NColorManagement::getPrimaries(primariesNamed);\n            return primaries;\n        }\n\n        float getTFMinLuminance(float sdrMinLuminance = -1.0f) const {\n            switch (transferFunction) {\n                case CM_TRANSFER_FUNCTION_EXT_LINEAR: return 0;\n                case CM_TRANSFER_FUNCTION_ST2084_PQ:\n                case CM_TRANSFER_FUNCTION_HLG: return HDR_MIN_LUMINANCE;\n                case CM_TRANSFER_FUNCTION_BT1886: return 0.01;\n                case CM_TRANSFER_FUNCTION_GAMMA22:\n                case CM_TRANSFER_FUNCTION_GAMMA28:\n                case CM_TRANSFER_FUNCTION_ST240:\n                case CM_TRANSFER_FUNCTION_LOG_100:\n                case CM_TRANSFER_FUNCTION_LOG_316:\n                case CM_TRANSFER_FUNCTION_XVYCC:\n                case CM_TRANSFER_FUNCTION_EXT_SRGB:\n                case CM_TRANSFER_FUNCTION_ST428:\n                case CM_TRANSFER_FUNCTION_SRGB:\n                default: return sdrMinLuminance >= 0 ? sdrMinLuminance : SDR_MIN_LUMINANCE;\n            }\n        };\n\n        float getTFMaxLuminance(int sdrMaxLuminance = -1) const {\n            switch (transferFunction) {\n                case CM_TRANSFER_FUNCTION_EXT_LINEAR:\n                    return SDR_MAX_LUMINANCE; // assume Windows scRGB. white color range 1.0 - 125.0 maps to SDR_MAX_LUMINANCE (80) - HDR_MAX_LUMINANCE (10000)\n                case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE;\n                case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE;\n                case CM_TRANSFER_FUNCTION_BT1886: return 100;\n                case CM_TRANSFER_FUNCTION_GAMMA22:\n                case CM_TRANSFER_FUNCTION_GAMMA28:\n                case CM_TRANSFER_FUNCTION_ST240:\n                case CM_TRANSFER_FUNCTION_LOG_100:\n                case CM_TRANSFER_FUNCTION_LOG_316:\n                case CM_TRANSFER_FUNCTION_XVYCC:\n                case CM_TRANSFER_FUNCTION_EXT_SRGB:\n                case CM_TRANSFER_FUNCTION_ST428:\n                case CM_TRANSFER_FUNCTION_SRGB:\n                default: return sdrMaxLuminance >= 0 ? sdrMaxLuminance : SDR_MAX_LUMINANCE;\n            }\n        };\n\n        float getTFRefLuminance(int sdrRefLuminance = -1) const {\n            switch (transferFunction) {\n                case CM_TRANSFER_FUNCTION_EXT_LINEAR:\n                case CM_TRANSFER_FUNCTION_ST2084_PQ:\n                case CM_TRANSFER_FUNCTION_HLG: return HDR_REF_LUMINANCE;\n                case CM_TRANSFER_FUNCTION_BT1886: return 100;\n                case CM_TRANSFER_FUNCTION_GAMMA22:\n                case CM_TRANSFER_FUNCTION_GAMMA28:\n                case CM_TRANSFER_FUNCTION_ST240:\n                case CM_TRANSFER_FUNCTION_LOG_100:\n                case CM_TRANSFER_FUNCTION_LOG_316:\n                case CM_TRANSFER_FUNCTION_XVYCC:\n                case CM_TRANSFER_FUNCTION_EXT_SRGB:\n                case CM_TRANSFER_FUNCTION_ST428:\n                case CM_TRANSFER_FUNCTION_SRGB:\n                default: return sdrRefLuminance >= 0 ? sdrRefLuminance : SDR_REF_LUMINANCE;\n            }\n        };\n    };\n\n    class CImageDescription {\n      public:\n        static WP<const CImageDescription> from(const SImageDescription& imageDescription);\n        static WP<const CImageDescription> from(const uint32_t imageDescriptionId);\n\n        WP<const CImageDescription>        with(const SImageDescription::SPCLuminances& luminances) const;\n\n        const SImageDescription&           value() const;\n        uint32_t                           id() const;\n\n        WP<const CPrimaries>               getPrimaries() const;\n\n      private:\n        CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId);\n        uint32_t          m_id          = 0;\n        uint32_t          m_primariesId = 0;\n        SImageDescription m_imageDescription;\n    };\n\n    using PImageDescription = WP<const CImageDescription>;\n\n    static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{\n        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22,\n        .primariesNameSet = true,\n        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,\n        .primaries        = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB),\n        .luminances       = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80},\n    });\n\n    static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{\n        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,\n        .primariesNameSet = true,\n        .primariesNamed   = NColorManagement::CM_PRIMARIES_BT2020,\n        .primaries        = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),\n        .luminances       = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203},\n    });\n\n    static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{\n        .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR,\n        .windowsScRGB     = true,\n        .primariesNameSet = true,\n        .primariesNamed   = NColorManagement::CM_PRIMARIES_SRGB,\n        .primaries        = NColorPrimaries::BT709,\n        .luminances       = {.reference = 203},\n    });\n\n    static const auto LINEAR_IMAGE_DESCRIPTION = SCRGB_IMAGE_DESCRIPTION; // TODO any reason to use something different?\n}\n"
  },
  {
    "path": "src/helpers/cm/ICC.cpp",
    "content": "#include \"ColorManagement.hpp\"\n#include \"../math/Math.hpp\"\n#include <cstddef>\n#include <fstream>\n\n#include \"../../debug/log/Logger.hpp\"\n#include \"../../render/Texture.hpp\"\n#include \"../../render/Renderer.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\nusing namespace Hyprutils::Utils;\n\n#include <lcms2.h>\n\nusing namespace NColorManagement;\n\nstatic std::vector<uint8_t> readBinary(const std::filesystem::path& file) {\n    std::ifstream ifs(file, std::ios::binary);\n    if (!ifs.good())\n        return {};\n\n    ifs.seekg(0, std::ios::end);\n    size_t len = ifs.tellg();\n    ifs.seekg(0, std::ios::beg);\n\n    if (len <= 0)\n        return {};\n\n    std::vector<uint8_t> buf;\n    buf.resize(len);\n    ifs.read(reinterpret_cast<char*>(buf.data()), len);\n\n    return buf;\n}\n\nstatic uint16_t bigEndianU16(const uint8_t* p) {\n    return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]);\n}\n\nstatic uint32_t bigEndianU32(const uint8_t* p) {\n    return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3];\n}\n\nstatic constexpr cmsTagSignature makeSig(char a, char b, char c, char d) {\n    return sc<cmsTagSignature>(sc<uint32_t>(a) << 24 | sc<uint32_t>(b) << 16 | sc<uint32_t>(c) << 8 | sc<uint32_t>(d));\n}\n\nstatic constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't');\n\n//\n\nstatic std::expected<std::optional<SVCGTTable16>, std::string> readVCGT16(cmsHPROFILE prof) {\n    if (!cmsIsTag(prof, VCGT_SIG))\n        return std::nullopt;\n\n    cmsUInt32Number n = cmsReadRawTag(prof, VCGT_SIG, nullptr, 0);\n    if (n < 8 + 4 + 2 + 2 + 2 + 2) // header + type + table header\n        return std::unexpected(\"Malformed vcgt tag\");\n\n    std::vector<uint8_t> raw(n);\n    if (cmsReadRawTag(prof, VCGT_SIG, raw.data(), n) != n)\n        return std::unexpected(\"Malformed vcgt tag\");\n\n    // raw layout:\n    // 0 ... 3: 'vcgt'\n    // 4 ... 7: reserved\n    // 8 ... 11: gammaType (0 = table)\n    uint32_t gammaType = bigEndianU32(raw.data() + 8);\n    if (gammaType != 0)\n        return std::unexpected(\"VCGT formula type is not supported by Hyprland\");\n\n    SVCGTTable16 table;\n    table.channels  = bigEndianU16(raw.data() + 12);\n    table.entries   = bigEndianU16(raw.data() + 14);\n    table.entrySize = bigEndianU16(raw.data() + 16);\n    // raw+18: reserved u16\n\n    Log::logger->log(Log::DEBUG, \"readVCGT16: table has {} channels, {} entries, and entry size of {}\", table.channels, table.entries, table.entrySize);\n\n    if (table.channels != 3 || table.entrySize != 2 || table.entries == 0)\n        return std::unexpected(\"invalid vcgt table size\");\n\n    size_t tableBytes = (size_t)table.channels * (size_t)table.entries * (size_t)table.entrySize;\n\n    // VCGT is a piece of shit and some absolute fucking mongoloid idiots\n    // decided it'd be great to have both 18 and 20\n    // FUCK YOU\n    size_t tableOff = 20;\n\n    auto   readTable = [&] -> void {\n        for (int c = 0; c < 3; ++c) {\n            table.ch[c].resize(table.entries);\n            for (uint16_t i = 0; i < table.entries; ++i) {\n                const uint8_t* p = raw.data() + tableOff + static_cast<ptrdiff_t>((c * table.entries + i) * 2);\n                table.ch[c][i]   = bigEndianU16(p); // 0 ... 65535\n            }\n        }\n    };\n\n    if (raw.size() < tableOff + tableBytes) {\n        tableOff = 18;\n\n        if (raw.size() < tableOff + tableBytes) {\n            Log::logger->log(Log::ERR, \"readVCGT16: table is too short, tag is invalid\");\n            return std::unexpected(\"table is too short\");\n        }\n\n        Log::logger->log(Log::DEBUG, \"readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18\");\n\n        readTable();\n    } else {\n        readTable();\n\n        // if the table's last entry is suspiciously low, we more than likely read an 18 as a 20.\n        if (table.ch[0][table.entries - 1] < 30000) {\n            Log::logger->log(Log::DEBUG, \"readVCGT16: table is likely offset 18 not 20, re-reading\");\n\n            tableOff = 18;\n\n            readTable();\n        }\n    }\n\n    if (table.ch[0][table.entries - 1] < 30000) {\n        Log::logger->log(Log::ERR, \"readVCGT16: table is malformed, last value of a gamma ramp can't be {}\", table.ch[0][table.entries - 1]);\n        return std::unexpected(\"invalid table values\");\n    }\n\n    Log::logger->log(Log::DEBUG, \"readVCGT16: red channel: [{}, {}, ... {}, {}]\", table.ch[0][0], table.ch[0][1], table.ch[0][table.entries - 2], table.ch[0][table.entries - 1]);\n    Log::logger->log(Log::DEBUG, \"readVCGT16: green channel: [{}, {}, ... {}, {}]\", table.ch[1][0], table.ch[1][1], table.ch[1][table.entries - 2], table.ch[1][table.entries - 1]);\n    Log::logger->log(Log::DEBUG, \"readVCGT16: blue channel: [{}, {}, ... {}, {}]\", table.ch[2][0], table.ch[2][1], table.ch[2][table.entries - 2], table.ch[2][table.entries - 1]);\n\n    return table;\n}\n\nstruct CmsProfileDeleter {\n    void operator()(cmsHPROFILE p) const {\n        if (p)\n            cmsCloseProfile(p);\n    }\n};\nstruct CmsTransformDeleter {\n    void operator()(cmsHTRANSFORM t) const {\n        if (t)\n            cmsDeleteTransform(t);\n    }\n};\n\nusing UniqueProfile   = std::unique_ptr<std::remove_pointer_t<cmsHPROFILE>, CmsProfileDeleter>;\nusing UniqueTransform = std::unique_ptr<std::remove_pointer_t<cmsHTRANSFORM>, CmsTransformDeleter>;\n\nstatic UniqueProfile createLinearSRGBProfile() {\n    cmsCIExyYTRIPLE prim{};\n    // sRGB / Rec.709 primaries\n    prim.Red.x   = 0.6400;\n    prim.Red.y   = 0.3300;\n    prim.Red.Y   = 1.0;\n    prim.Green.x = 0.3000;\n    prim.Green.y = 0.6000;\n    prim.Green.Y = 1.0;\n    prim.Blue.x  = 0.1500;\n    prim.Blue.y  = 0.0600;\n    prim.Blue.Y  = 1.0;\n\n    cmsCIExyY wp{};\n    wp.x = 0.3127;\n    wp.y = 0.3290;\n    wp.Y = 1.0; // D65\n\n    cmsToneCurve* lin       = cmsBuildGamma(nullptr, 1.0);\n    cmsToneCurve* curves[3] = {lin, lin, lin};\n\n    cmsHPROFILE   p = cmsCreateRGBProfile(&wp, &prim, curves);\n\n    cmsFreeToneCurve(lin);\n    return UniqueProfile{p};\n}\n\nstatic std::expected<void, std::string> buildIcc3DLut(cmsHPROFILE profile, SImageDescription& image) {\n    UniqueProfile src = createLinearSRGBProfile();\n    if (!src)\n        return std::unexpected(\"Failed to create linear sRGB profile\");\n\n    // Rendering intent: RELATIVE_COLORIMETRIC is common for displays; add BPC to be safe.\n    const int             intent = INTENT_RELATIVE_COLORIMETRIC;\n    const cmsUInt32Number flags  = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_HIGHRESPRECALC; // good quality precalc in LCMS\n\n    // float->float transform (linear input, encoded output in dst device space)\n    UniqueTransform xform{cmsCreateTransform(src.get(), TYPE_RGB_FLT, profile, TYPE_RGB_FLT, intent, flags)};\n    if (!xform)\n        return std::unexpected(\"Failed to create ICC transform\");\n\n    Log::logger->log(Log::DEBUG, \"Building a {}³ 3D LUT\", image.icc.lutSize);\n\n    image.icc.present = true;\n    image.icc.lutDataPacked.resize(image.icc.lutSize * image.icc.lutSize * image.icc.lutSize * 3);\n\n    auto idx = [&image](int r, int g, int b) -> size_t {\n        //\n        return ((size_t)b * image.icc.lutSize * image.icc.lutSize + (size_t)g * image.icc.lutSize + (size_t)r) * 3;\n    };\n\n    for (size_t bz = 0; bz < image.icc.lutSize; ++bz) {\n        for (size_t gy = 0; gy < image.icc.lutSize; ++gy) {\n            for (size_t rx = 0; rx < image.icc.lutSize; ++rx) {\n                float in[3] = {\n                    rx / float(image.icc.lutSize - 1),\n                    gy / float(image.icc.lutSize - 1),\n                    bz / float(image.icc.lutSize - 1),\n                };\n                float outRGB[3];\n                cmsDoTransform(xform.get(), in, outRGB, 1);\n\n                outRGB[0] = std::clamp(outRGB[0], 0.F, 1.F);\n                outRGB[1] = std::clamp(outRGB[1], 0.F, 1.F);\n                outRGB[2] = std::clamp(outRGB[2], 0.F, 1.F);\n\n                const size_t o                 = idx(rx, gy, bz);\n                image.icc.lutDataPacked[o + 0] = outRGB[0];\n                image.icc.lutDataPacked[o + 1] = outRGB[1];\n                image.icc.lutDataPacked[o + 2] = outRGB[2];\n            }\n        }\n    }\n\n    Log::logger->log(Log::DEBUG, \"3D LUT constructed, size {}\", image.icc.lutDataPacked.size());\n\n    // upload\n    image.icc.lutTexture = g_pHyprRenderer->createTexture(image.icc.lutDataPacked, image.icc.lutSize);\n\n    return {};\n}\n\nstd::expected<SImageDescription, std::string> SImageDescription::fromICC(const std::filesystem::path& file) {\n    static auto     PVCGTENABLED = CConfigValue<Hyprlang::INT>(\"render:icc_vcgt_enabled\");\n\n    std::error_code ec;\n    if (!std::filesystem::exists(file, ec) || ec)\n        return std::unexpected(\"Invalid file\");\n\n    SImageDescription image;\n    image.rawICC = readBinary(file);\n\n    if (image.rawICC.empty())\n        return std::unexpected(\"Failed to read file\");\n\n    cmsHPROFILE prof = cmsOpenProfileFromFile(file.string().c_str(), \"r\");\n    if (!prof)\n        return std::unexpected(\"CMS failed to open icc file\");\n\n    CScopeGuard x([&prof] { cmsCloseProfile(prof); });\n\n    // only handle RGB (typical display profiles)\n    if (cmsGetColorSpace(prof) != cmsSigRgbData)\n        return std::unexpected(\"Only RGB display profiles are supported\");\n\n    Log::logger->log(Log::DEBUG, \"============= Begin ICC load =============\");\n    Log::logger->log(Log::DEBUG, \"ICC size: {} bytes\", image.rawICC.size());\n\n    if (const auto RET = buildIcc3DLut(prof, image); !RET)\n        return std::unexpected(RET.error());\n\n    if (*PVCGTENABLED) {\n        auto vcgtRes = readVCGT16(prof);\n        if (!vcgtRes)\n            return std::unexpected(vcgtRes.error());\n\n        image.icc.vcgt = *vcgtRes;\n\n        if (!*vcgtRes)\n            Log::logger->log(Log::DEBUG, \"ICC profile has no VCGT data\");\n    } else\n        Log::logger->log(Log::DEBUG, \"Skipping VCGT load, disabled by config\");\n\n    Log::logger->log(Log::DEBUG, \"============= End ICC load =============\");\n\n    return image;\n}"
  },
  {
    "path": "src/helpers/defer/Promise.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <string>\n#include \"../memory/Memory.hpp\"\n\n// TODO: move into hyprutils\n\ntemplate <typename T>\nclass CPromise;\ntemplate <typename T>\nclass CPromiseResult;\n\ntemplate <typename T>\nclass CPromiseResolver {\n  public:\n    CPromiseResolver(const CPromiseResolver&)            = delete;\n    CPromiseResolver(CPromiseResolver&&)                 = delete;\n    CPromiseResolver& operator=(const CPromiseResolver&) = delete;\n    CPromiseResolver& operator=(CPromiseResolver&&)      = delete;\n\n    void              resolve(T value) {\n        if (m_promise->m_result)\n            return;\n\n        m_promise->m_result = CPromiseResult<T>::result(value);\n\n        if (!m_promise->m_then)\n            return;\n\n        m_promise->m_then(m_promise->m_result);\n    }\n\n    void reject(const std::string& reason) {\n        if (m_promise->m_result)\n            return;\n\n        m_promise->m_result = CPromiseResult<T>::err(reason);\n\n        if (!m_promise->m_then)\n            return;\n\n        m_promise->m_then(m_promise->m_result);\n    }\n\n  private:\n    CPromiseResolver(SP<CPromise<T>> promise) : m_promise(promise) {}\n\n    SP<CPromise<T>> m_promise;\n\n    friend class CPromise<T>;\n};\n\ntemplate <typename T>\nclass CPromiseResult {\n  public:\n    bool hasError() {\n        return m_hasError;\n    }\n\n    T result() {\n        return m_result;\n    }\n\n    std::string error() {\n        return m_error;\n    }\n\n  private:\n    static SP<CPromiseResult<T>> result(T result) {\n        auto p      = SP<CPromiseResult<T>>(new CPromiseResult<T>());\n        p->m_result = result;\n        return p;\n    }\n\n    static SP<CPromiseResult<T>> err(std::string reason) {\n        auto p        = SP<CPromiseResult<T>>(new CPromiseResult<T>());\n        p->m_error    = reason;\n        p->m_hasError = true;\n        return p;\n    }\n\n    T           m_result   = {};\n    std::string m_error    = {};\n    bool        m_hasError = false;\n\n    friend class CPromiseResolver<T>;\n};\n\ntemplate <typename T>\nclass CPromise {\n  public:\n    CPromise(const CPromise&)                      = delete;\n    CPromise(CPromise&&)                           = delete;\n    CPromise&           operator=(const CPromise&) = delete;\n    CPromise&           operator=(CPromise&&)      = delete;\n\n    static SP<CPromise> make(const std::function<void(SP<CPromiseResolver<T>>)>& fn) {\n        auto sp = SP<CPromise<T>>(new CPromise<T>());\n        fn(SP<CPromiseResolver<T>>(new CPromiseResolver<T>(sp)));\n        return sp;\n    }\n\n    void then(std::function<void(SP<CPromiseResult<T>>)>&& fn) {\n        m_then = std::move(fn);\n        if (m_result)\n            m_then(m_result);\n    }\n\n  private:\n    CPromise() = default;\n\n    const std::function<void(SP<CPromiseResult<T>>)> m_fn;\n    std::function<void(SP<CPromiseResult<T>>)>       m_then;\n    SP<CPromiseResult<T>>                            m_result;\n\n    friend class CPromiseResult<T>;\n    friend class CPromiseResolver<T>;\n};"
  },
  {
    "path": "src/helpers/env/Env.cpp",
    "content": "#include \"Env.hpp\"\n\n#include <cstdlib>\n#include <string_view>\n\nbool Env::envEnabled(const std::string& env) {\n    auto ret = getenv(env.c_str());\n    if (!ret)\n        return false;\n\n    const std::string_view sv = ret;\n\n    return !sv.empty() && sv != \"0\";\n}\n\nbool Env::isTrace() {\n    static bool TRACE = envEnabled(\"HYPRLAND_TRACE\");\n    return TRACE;\n}\n"
  },
  {
    "path": "src/helpers/env/Env.hpp",
    "content": "#pragma once\n\n#include <string>\n\nnamespace Env {\n    bool envEnabled(const std::string& env);\n    bool isTrace();\n}\n"
  },
  {
    "path": "src/helpers/fs/FsUtils.cpp",
    "content": "#include \"FsUtils.hpp\"\n#include \"../../debug/log/Logger.hpp\"\n\n#include <cstdlib>\n#include <filesystem>\n#include <fstream>\n\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/string/VarList.hpp>\nusing namespace Hyprutils::String;\n\nstd::optional<std::string> NFsUtils::getDataHome() {\n    const auto  DATA_HOME = getenv(\"XDG_DATA_HOME\");\n\n    std::string dataRoot;\n\n    if (!DATA_HOME) {\n        const auto HOME = getenv(\"HOME\");\n\n        if (!HOME) {\n            Log::logger->log(Log::ERR, \"FsUtils::getDataHome: can't get data home: no $HOME or $XDG_DATA_HOME\");\n            return std::nullopt;\n        }\n\n        dataRoot = HOME + std::string{\"/.local/share/\"};\n    } else\n        dataRoot = DATA_HOME + std::string{\"/\"};\n\n    std::error_code ec;\n    if (!std::filesystem::exists(dataRoot, ec) || ec) {\n        Log::logger->log(Log::ERR, \"FsUtils::getDataHome: can't get data home: inaccessible / missing\");\n        return std::nullopt;\n    }\n\n    dataRoot += \"hyprland/\";\n\n    if (!std::filesystem::exists(dataRoot, ec) || ec) {\n        Log::logger->log(Log::DEBUG, \"FsUtils::getDataHome: no hyprland data home, creating.\");\n        std::filesystem::create_directory(dataRoot, ec);\n        if (ec) {\n            Log::logger->log(Log::ERR, \"FsUtils::getDataHome: can't create new data home for hyprland\");\n            return std::nullopt;\n        }\n        std::filesystem::permissions(dataRoot, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec, ec);\n        if (ec)\n            Log::logger->log(Log::WARN, \"FsUtils::getDataHome: couldn't set perms on hyprland data store. Proceeding anyways.\");\n    }\n\n    if (!std::filesystem::exists(dataRoot, ec) || ec) {\n        Log::logger->log(Log::ERR, \"FsUtils::getDataHome: no hyprland data home, failed to create.\");\n        return std::nullopt;\n    }\n\n    return dataRoot;\n}\n\nstd::optional<std::string> NFsUtils::readFileAsString(const std::string& path) {\n    std::error_code ec;\n\n    if (!std::filesystem::exists(path, ec) || ec)\n        return std::nullopt;\n\n    std::ifstream file(path);\n    if (!file.good())\n        return std::nullopt;\n\n    return trim(std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())));\n}\n\nbool NFsUtils::writeToFile(const std::string& path, const std::string& content) {\n    std::ofstream of(path, std::ios::trunc);\n    if (!of.good()) {\n        Log::logger->log(Log::ERR, \"CVersionKeeperManager: couldn't open an ofstream for writing the version file.\");\n        return false;\n    }\n\n    of << content;\n    of.close();\n\n    return true;\n}\n\nbool NFsUtils::executableExistsInPath(const std::string& exe) {\n    if (!getenv(\"PATH\"))\n        return false;\n\n    static CVarList paths(getenv(\"PATH\"), 0, ':', true);\n\n    for (auto& p : paths) {\n        std::string     path = p + std::string{\"/\"} + exe;\n        std::error_code ec;\n        if (!std::filesystem::exists(path, ec) || ec)\n            continue;\n\n        if (!std::filesystem::is_regular_file(path, ec) || ec)\n            continue;\n\n        auto stat = std::filesystem::status(path, ec);\n        if (ec)\n            continue;\n\n        auto perms = stat.permissions();\n\n        return std::filesystem::perms::none != (perms & std::filesystem::perms::others_exec);\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/helpers/fs/FsUtils.hpp",
    "content": "#pragma once\n#include <optional>\n#include <string>\n\nnamespace NFsUtils {\n    // Returns the path to the hyprland directory in data home.\n    std::optional<std::string> getDataHome();\n\n    std::optional<std::string> readFileAsString(const std::string& path);\n\n    // overwrites the file if exists\n    bool writeToFile(const std::string& path, const std::string& content);\n\n    bool executableExistsInPath(const std::string& exe);\n};\n"
  },
  {
    "path": "src/helpers/math/Direction.cpp",
    "content": ""
  },
  {
    "path": "src/helpers/math/Direction.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n\nnamespace Math {\n    enum eDirection : int8_t {\n        DIRECTION_DEFAULT = -1,\n        DIRECTION_UP,\n        DIRECTION_RIGHT,\n        DIRECTION_DOWN,\n        DIRECTION_LEFT\n    };\n\n    inline eDirection fromChar(char x) {\n        switch (x) {\n            case 'r': return DIRECTION_RIGHT;\n            case 'l': return DIRECTION_LEFT;\n            case 't':\n            case 'u': return DIRECTION_UP;\n            case 'b':\n            case 'd': return DIRECTION_DOWN;\n            default: return DIRECTION_DEFAULT;\n        }\n    }\n\n    inline const char* toString(eDirection d) {\n        switch (d) {\n            case DIRECTION_UP: return \"up\";\n            case DIRECTION_DOWN: return \"down\";\n            case DIRECTION_LEFT: return \"left\";\n            case DIRECTION_RIGHT: return \"right\";\n            default: return \"default\";\n        }\n    }\n};"
  },
  {
    "path": "src/helpers/math/Expression.cpp",
    "content": "#include \"Expression.hpp\"\n#include \"muParser.h\"\n#include \"../../debug/log/Logger.hpp\"\n\nusing namespace Math;\n\nCExpression::CExpression() : m_parser(makeUnique<mu::Parser>()) {\n    ;\n}\n\nvoid CExpression::addVariable(const std::string& name, double val) {\n    m_parser->DefineConst(name, val);\n}\n\nstd::optional<double> CExpression::compute(const std::string& expr) {\n    try {\n        m_parser->SetExpr(expr);\n        return m_parser->Eval();\n    } catch (mu::Parser::exception_type& e) { Log::logger->log(Log::ERR, \"CExpression::compute: mu threw: {}\", e.GetMsg()); }\n\n    return std::nullopt;\n}\n"
  },
  {
    "path": "src/helpers/math/Expression.hpp",
    "content": "#pragma once\n\n#include \"../memory/Memory.hpp\"\n#include <string>\n#include <optional>\n\nnamespace mu {\n    class Parser;\n};\n\nnamespace Math {\n    class CExpression {\n      public:\n        CExpression();\n        ~CExpression() = default;\n\n        CExpression(const CExpression&) = delete;\n        CExpression(CExpression&)       = delete;\n        CExpression(CExpression&&)      = delete;\n\n        void                  addVariable(const std::string& name, double val);\n\n        std::optional<double> compute(const std::string& expr);\n\n      private:\n        UP<mu::Parser> m_parser;\n    };\n};"
  },
  {
    "path": "src/helpers/math/Math.cpp",
    "content": "#include \"Math.hpp\"\n#include \"../memory/Memory.hpp\"\n#include \"../../macros.hpp\"\n\n#include <unordered_map>\n#include <array>\n\nusing namespace Math;\n\n// FIXME: expose in hu\nstatic std::unordered_map<eTransform, Mat3x3> transforms = {\n    {HYPRUTILS_TRANSFORM_NORMAL, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_90, std::array<float, 9>{0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_180, std::array<float, 9>{-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_270, std::array<float, 9>{0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_FLIPPED, std::array<float, 9>{-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_FLIPPED_90, std::array<float, 9>{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_FLIPPED_180, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n    {HYPRUTILS_TRANSFORM_FLIPPED_270, std::array<float, 9>{0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},\n};\n\neTransform Math::wlTransformToHyprutils(wl_output_transform t) {\n    switch (t) {\n        case WL_OUTPUT_TRANSFORM_NORMAL: return eTransform::HYPRUTILS_TRANSFORM_NORMAL;\n        case WL_OUTPUT_TRANSFORM_180: return eTransform::HYPRUTILS_TRANSFORM_180;\n        case WL_OUTPUT_TRANSFORM_90: return eTransform::HYPRUTILS_TRANSFORM_90;\n        case WL_OUTPUT_TRANSFORM_270: return eTransform::HYPRUTILS_TRANSFORM_270;\n        case WL_OUTPUT_TRANSFORM_FLIPPED: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED;\n        case WL_OUTPUT_TRANSFORM_FLIPPED_180: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180;\n        case WL_OUTPUT_TRANSFORM_FLIPPED_270: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270;\n        case WL_OUTPUT_TRANSFORM_FLIPPED_90: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90;\n        default: break;\n    }\n    return eTransform::HYPRUTILS_TRANSFORM_NORMAL;\n}\n\nwl_output_transform Math::invertTransform(wl_output_transform tr) {\n    if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED))\n        tr = sc<wl_output_transform>(tr ^ sc<int>(WL_OUTPUT_TRANSFORM_180));\n\n    return tr;\n}\n\nstatic bool matEq(const Mat3x3& a, const Mat3x3& b) {\n    for (size_t i = 0; i < 9; ++i) {\n        const float Δ = std::fabs(a.getMatrix()[i] - b.getMatrix()[i]);\n        if (Δ > 1e-6) // eps\n            return false;\n    }\n    return true;\n}\n\nstatic eTransform composeInternal(eTransform a, eTransform b) {\n    const auto& A      = transforms.at(a);\n    const auto& B      = transforms.at(b);\n    const auto  RESULT = Mat3x3{A}.multiply(B);\n\n    for (const auto& [t, M] : transforms) {\n        if (matEq(M, RESULT))\n            return t;\n    }\n\n    return eTransform::HYPRUTILS_TRANSFORM_NORMAL;\n}\n\neTransform Math::composeTransform(eTransform a, eTransform b) {\n    static std::array<std::array<eTransform, 8>, 8> lookup;\n    static bool                                     once = true;\n\n    if (once) {\n        once = false;\n\n        // bake the composition table\n        static_assert(HYPRUTILS_TRANSFORM_FLIPPED_270 == 7);\n        for (size_t i = 0; i <= HYPRUTILS_TRANSFORM_FLIPPED_270 /* 7 */; ++i) {\n            for (size_t j = 0; j <= HYPRUTILS_TRANSFORM_FLIPPED_270 /* 7 */; ++j) {\n                lookup[i][j] = composeInternal(sc<eTransform>(i), sc<eTransform>(j));\n            }\n        }\n    }\n\n    RASSERT(a >= HYPRUTILS_TRANSFORM_NORMAL && a <= HYPRUTILS_TRANSFORM_FLIPPED_270, \"Invalid transform a in composeTransform\");\n    RASSERT(b >= HYPRUTILS_TRANSFORM_NORMAL && b <= HYPRUTILS_TRANSFORM_FLIPPED_270, \"Invalid transform b in composeTransform\");\n\n    return lookup[a][b];\n}\n"
  },
  {
    "path": "src/helpers/math/Math.hpp",
    "content": "#pragma once\n\n#include <wayland-server-protocol.h>\n\n// includes box and vector as well\n#include <hyprutils/math/Region.hpp>\n#include <hyprutils/math/Mat3x3.hpp>\n\n// NOLINTNEXTLINE\nusing namespace Hyprutils::Math;\n\nnamespace Math {\n    constexpr const Vector2D VECTOR2D_MAX = {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()};\n\n    eTransform               wlTransformToHyprutils(wl_output_transform t);\n    wl_output_transform      invertTransform(wl_output_transform tr);\n    eTransform               composeTransform(eTransform a, eTransform b);\n}"
  },
  {
    "path": "src/helpers/memory/Memory.hpp",
    "content": "#pragma once\n\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <hyprutils/memory/Atomic.hpp>\n\nusing namespace Hyprutils::Memory;\n\ntemplate <typename T>\nusing SP = Hyprutils::Memory::CSharedPointer<T>;\ntemplate <typename T>\nusing WP = Hyprutils::Memory::CWeakPointer<T>;\ntemplate <typename T>\nusing UP = Hyprutils::Memory::CUniquePointer<T>;\ntemplate <typename T>\nusing ASP = Hyprutils::Memory::CAtomicSharedPointer<T>;\n"
  },
  {
    "path": "src/helpers/signal/Signal.hpp",
    "content": "#pragma once\n\n#include <hyprutils/signal/Signal.hpp>\n\n//NOLINTNEXTLINE\nusing namespace Hyprutils::Signal;\n"
  },
  {
    "path": "src/helpers/sync/SyncReleaser.cpp",
    "content": "#include \"SyncReleaser.hpp\"\n#include \"SyncTimeline.hpp\"\n#include \"../../render/OpenGL.hpp\"\n#include <sys/ioctl.h>\n\n#if defined(__linux__)\n#include <linux/sync_file.h>\n#else\nstruct sync_merge_data {\n    char  name[32];\n    __s32 fd2;\n    __s32 fence;\n    __u32 flags;\n    __u32 pad;\n};\n#define SYNC_IOC_MAGIC '>'\n#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 3, struct sync_merge_data)\n#endif\n\nusing namespace Hyprutils::OS;\n\nCSyncReleaser::CSyncReleaser(SP<CSyncTimeline> timeline, uint64_t point) : m_timeline(timeline), m_point(point) {\n    ;\n}\n\nCSyncReleaser::~CSyncReleaser() {\n    if (!m_timeline) {\n        Log::logger->log(Log::ERR, \"CSyncReleaser destructing without a timeline\");\n        return;\n    }\n\n    if (m_fd.isValid())\n        m_timeline->importFromSyncFileFD(m_point, m_fd);\n    else\n        m_timeline->signal(m_point);\n}\n\nstatic CFileDescriptor mergeSyncFds(const CFileDescriptor& fd1, const CFileDescriptor& fd2) {\n    // combines the fences of both sync_fds into a dma_fence_array (https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html#c.dma_fence_array_create)\n    // with the signal_on_any param set to false, so the new sync_fd will \"signal when all fences in the array signal.\"\n\n    struct sync_merge_data data{\n        .name  = \"merged release fence\",\n        .fd2   = fd2.get(),\n        .fence = -1,\n    };\n    int err = -1;\n    do {\n        err = ioctl(fd1.get(), SYNC_IOC_MERGE, &data);\n    } while (err == -1 && (errno == EINTR || errno == EAGAIN));\n    if (err < 0)\n        return CFileDescriptor{};\n    else\n        return CFileDescriptor(data.fence);\n}\n\nvoid CSyncReleaser::addSyncFileFd(const Hyprutils::OS::CFileDescriptor& syncFd) {\n    if (m_fd.isValid())\n        m_fd = mergeSyncFds(m_fd, syncFd);\n    else\n        m_fd = syncFd.duplicate();\n}\n\nvoid CSyncReleaser::drop() {\n    m_timeline.reset();\n}\n"
  },
  {
    "path": "src/helpers/sync/SyncReleaser.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <optional>\n#include <vector>\n#include <functional>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include \"../memory/Memory.hpp\"\n\n/*\n    A helper (inspired by KDE's KWin) that will release the timeline point in the dtor\n*/\n\nclass CSyncTimeline;\n\nclass CSyncReleaser {\n  public:\n    CSyncReleaser(SP<CSyncTimeline> timeline, uint64_t point);\n    ~CSyncReleaser();\n\n    // drops the releaser, will never signal anymore\n    void drop();\n\n    // wait for this sync_fd to signal before releasing\n    void addSyncFileFd(const Hyprutils::OS::CFileDescriptor& syncFd);\n\n  private:\n    SP<CSyncTimeline>              m_timeline;\n    uint64_t                       m_point = 0;\n    Hyprutils::OS::CFileDescriptor m_fd;\n};\n"
  },
  {
    "path": "src/helpers/sync/SyncTimeline.cpp",
    "content": "#include \"SyncTimeline.hpp\"\n#include \"../../defines.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../Compositor.hpp\"\n\n#include <xf86drm.h>\n#include <sys/eventfd.h>\nusing namespace Hyprutils::OS;\n\nSP<CSyncTimeline> CSyncTimeline::create(int drmFD_) {\n    if (!g_pCompositor->supportsDrmSyncobjTimeline())\n        return nullptr;\n\n    auto timeline     = SP<CSyncTimeline>(new CSyncTimeline);\n    timeline->m_drmFD = drmFD_;\n    timeline->m_self  = timeline;\n\n    if (drmSyncobjCreate(drmFD_, 0, &timeline->m_handle)) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline: failed to create a drm syncobj??\");\n        return nullptr;\n    }\n\n    return timeline;\n}\n\nSP<CSyncTimeline> CSyncTimeline::create(int drmFD_, CFileDescriptor&& drmSyncobjFD) {\n    if (!g_pCompositor->supportsDrmSyncobjTimeline())\n        return nullptr;\n\n    auto timeline         = SP<CSyncTimeline>(new CSyncTimeline);\n    timeline->m_drmFD     = drmFD_;\n    timeline->m_syncobjFD = std::move(drmSyncobjFD);\n    timeline->m_self      = timeline;\n\n    if (drmSyncobjFDToHandle(drmFD_, timeline->m_syncobjFD.get(), &timeline->m_handle)) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline: failed to create a drm syncobj from fd??\");\n        return nullptr;\n    }\n\n    return timeline;\n}\n\nCSyncTimeline::~CSyncTimeline() {\n    if (m_handle == 0)\n        return;\n\n    drmSyncobjDestroy(m_drmFD, m_handle);\n}\n\nstd::optional<bool> CSyncTimeline::check(uint64_t point, uint32_t flags) {\n#ifdef __FreeBSD__\n    constexpr int ETIME_ERR = ETIMEDOUT;\n#else\n    constexpr int ETIME_ERR = ETIME;\n#endif\n\n    uint32_t signaled = 0;\n    int      ret      = drmSyncobjTimelineWait(m_drmFD, &m_handle, &point, 1, 0, flags, &signaled);\n    if (ret != 0 && ret != -ETIME_ERR) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline::check: drmSyncobjTimelineWait failed\");\n        return std::nullopt;\n    }\n\n    return ret == 0;\n}\n\nbool CSyncTimeline::addWaiter(std::function<void()>&& waiter, uint64_t point, uint32_t flags) {\n    auto eventFd = CFileDescriptor(eventfd(0, EFD_CLOEXEC));\n\n    if (!eventFd.isValid()) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline::addWaiter: failed to acquire an eventfd\");\n        return false;\n    }\n\n    if (drmSyncobjEventfd(m_drmFD, m_handle, point, eventFd.get(), flags)) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline::addWaiter: drmSyncobjEventfd failed\");\n        return false;\n    }\n\n    g_pEventLoopManager->doOnReadable(std::move(eventFd), std::move(waiter));\n\n    return true;\n}\n\nCFileDescriptor CSyncTimeline::exportAsSyncFileFD(uint64_t src) {\n    int      sync = -1;\n\n    uint32_t syncHandle = 0;\n    if (drmSyncobjCreate(m_drmFD, 0, &syncHandle)) {\n        Log::logger->log(Log::ERR, \"exportAsSyncFileFD: drmSyncobjCreate failed\");\n        return {};\n    }\n\n    if (drmSyncobjTransfer(m_drmFD, syncHandle, 0, m_handle, src, 0)) {\n        Log::logger->log(Log::ERR, \"exportAsSyncFileFD: drmSyncobjTransfer failed\");\n        drmSyncobjDestroy(m_drmFD, syncHandle);\n        return {};\n    }\n\n    if (drmSyncobjExportSyncFile(m_drmFD, syncHandle, &sync)) {\n        Log::logger->log(Log::ERR, \"exportAsSyncFileFD: drmSyncobjExportSyncFile failed\");\n        drmSyncobjDestroy(m_drmFD, syncHandle);\n        return {};\n    }\n\n    drmSyncobjDestroy(m_drmFD, syncHandle);\n    return CFileDescriptor{sync};\n}\n\nbool CSyncTimeline::importFromSyncFileFD(uint64_t dst, CFileDescriptor& fd) {\n    uint32_t syncHandle = 0;\n\n    if (drmSyncobjCreate(m_drmFD, 0, &syncHandle)) {\n        Log::logger->log(Log::ERR, \"importFromSyncFileFD: drmSyncobjCreate failed\");\n        return false;\n    }\n\n    if (drmSyncobjImportSyncFile(m_drmFD, syncHandle, fd.get())) {\n        Log::logger->log(Log::ERR, \"importFromSyncFileFD: drmSyncobjImportSyncFile failed\");\n        drmSyncobjDestroy(m_drmFD, syncHandle);\n        return false;\n    }\n\n    if (drmSyncobjTransfer(m_drmFD, m_handle, dst, syncHandle, 0, 0)) {\n        Log::logger->log(Log::ERR, \"importFromSyncFileFD: drmSyncobjTransfer failed\");\n        drmSyncobjDestroy(m_drmFD, syncHandle);\n        return false;\n    }\n\n    drmSyncobjDestroy(m_drmFD, syncHandle);\n    return true;\n}\n\nbool CSyncTimeline::transfer(SP<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint) {\n    if (m_drmFD != from->m_drmFD) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline::transfer: cannot transfer timelines between gpus, {} -> {}\", from->m_drmFD, m_drmFD);\n        return false;\n    }\n\n    if (drmSyncobjTransfer(m_drmFD, m_handle, toPoint, from->m_handle, fromPoint, 0)) {\n        Log::logger->log(Log::ERR, \"CSyncTimeline::transfer: drmSyncobjTransfer failed\");\n        return false;\n    }\n\n    return true;\n}\n\nvoid CSyncTimeline::signal(uint64_t point) {\n    if (drmSyncobjTimelineSignal(m_drmFD, &m_handle, &point, 1))\n        Log::logger->log(Log::ERR, \"CSyncTimeline::signal: drmSyncobjTimelineSignal failed\");\n}\n"
  },
  {
    "path": "src/helpers/sync/SyncTimeline.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <optional>\n#include <vector>\n#include <functional>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include \"../memory/Memory.hpp\"\n\n/*\n    Hyprland synchronization timelines are based on the wlroots' ones, which\n    are based on Vk timeline semaphores: https://www.khronos.org/blog/vulkan-timeline-semaphores\n*/\n\nstruct wl_event_source;\n\nclass CSyncTimeline {\n  public:\n    static SP<CSyncTimeline> create(int drmFD_);\n    static SP<CSyncTimeline> create(int drmFD_, Hyprutils::OS::CFileDescriptor&& drmSyncobjFD);\n    ~CSyncTimeline();\n\n    // check if the timeline point has been signaled\n    // flags: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT or DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE\n    // std::nullopt on fail\n    std::optional<bool>            check(uint64_t point, uint32_t flags);\n\n    bool                           addWaiter(std::function<void()>&& waiter, uint64_t point, uint32_t flags);\n    Hyprutils::OS::CFileDescriptor exportAsSyncFileFD(uint64_t src);\n    bool                           importFromSyncFileFD(uint64_t dst, Hyprutils::OS::CFileDescriptor& fd);\n    bool                           transfer(SP<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint);\n    void                           signal(uint64_t point);\n\n    int                            m_drmFD = -1;\n    Hyprutils::OS::CFileDescriptor m_syncobjFD;\n    uint32_t                       m_handle = 0;\n    WP<CSyncTimeline>              m_self;\n\n  private:\n    CSyncTimeline() = default;\n};\n"
  },
  {
    "path": "src/helpers/time/Time.cpp",
    "content": "#include \"Time.hpp\"\n\n#define chr                   std::chrono\n#define TIMESPEC_NSEC_PER_SEC 1000000000L\n\nusing s_ns = std::pair<uint64_t, uint64_t>;\n\nstatic s_ns timediff(const s_ns& a, const s_ns& b) {\n    s_ns d;\n\n    d.first = a.first - b.first;\n    if (a.second >= b.second)\n        d.second = a.second - b.second;\n    else {\n        d.second = (TIMESPEC_NSEC_PER_SEC + a.second) - b.second;\n        d.first -= 1;\n    }\n\n    return d;\n}\n\nstatic s_ns timeadd(const s_ns& a, const s_ns& b) {\n    s_ns d;\n\n    d.first = a.first + b.first;\n    if (a.second + b.second >= TIMESPEC_NSEC_PER_SEC) {\n        d.second = a.second + b.second - TIMESPEC_NSEC_PER_SEC;\n        d.first += 1;\n    } else\n        d.second = a.second + b.second;\n\n    return d;\n}\n\nTime::steady_tp Time::steadyNow() {\n    return chr::steady_clock::now();\n}\n\nTime::system_tp Time::systemNow() {\n    return chr::system_clock::now();\n}\n\nuint64_t Time::millis(const steady_tp& tp) {\n    return chr::duration_cast<chr::milliseconds>(tp.time_since_epoch()).count();\n}\n\ns_ns Time::secNsec(const steady_tp& tp) {\n    const uint64_t sec     = chr::duration_cast<chr::seconds>(tp.time_since_epoch()).count();\n    const auto     nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec));\n    return {sec, chr::duration_cast<chr::nanoseconds>(nsecdur).count()};\n}\n\nuint64_t Time::millis(const system_tp& tp) {\n    return chr::duration_cast<chr::milliseconds>(tp.time_since_epoch()).count();\n}\n\ns_ns Time::secNsec(const system_tp& tp) {\n    const uint64_t sec     = chr::duration_cast<chr::seconds>(tp.time_since_epoch()).count();\n    const auto     nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec));\n    return {sec, chr::duration_cast<chr::nanoseconds>(nsecdur).count()};\n}\n\n// TODO: this is a mess, but C++ doesn't define what steady_clock is.\n// At least on Linux, system_clock == CLOCK_REALTIME\n// and steady_clock == CLOCK_MONOTONIC,\n// or at least it seems so with gcc and gcc's stl.\n// but, since we can't *ever* be sure, we have to guess.\n// In general, this may shift the time around by a couple hundred ns. Doesn't matter, realistically.\n\nTime::steady_tp Time::fromTimespec(const timespec* ts) {\n    timespec mono{}, real{};\n    clock_gettime(CLOCK_MONOTONIC, &mono);\n    clock_gettime(CLOCK_REALTIME, &real);\n    auto now    = Time::steadyNow();\n    auto nowSys = Time::systemNow();\n    s_ns stdSteady, stdReal;\n    stdSteady = Time::secNsec(now);\n    stdReal   = Time::secNsec(nowSys);\n\n    // timespec difference, REAL - MONO\n    s_ns diff;\n    diff.first = real.tv_sec - mono.tv_sec;\n    if (real.tv_nsec >= mono.tv_nsec)\n        diff.second = real.tv_nsec - mono.tv_nsec;\n    else {\n        diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec;\n        diff.first -= 1;\n    }\n\n    // STD difference, REAL - MONO\n    s_ns diff2 = timediff(stdReal, stdSteady);\n\n    s_ns diffFinal;\n    s_ns monotime = {ts->tv_sec, ts->tv_nsec};\n\n    if (diff.first >= diff2.first || (diff.first == diff2.first && diff.second >= diff2.second))\n        diffFinal = timediff(diff, diff2);\n    else\n        diffFinal = timediff(diff2, diff);\n\n    auto sum = timeadd(monotime, diffFinal);\n    return chr::steady_clock::time_point(std::chrono::seconds(sum.first)) + chr::nanoseconds(sum.second);\n}\n\nstruct timespec Time::toTimespec(const steady_tp& tp) {\n    timespec mono{}, real{};\n    clock_gettime(CLOCK_MONOTONIC, &mono);\n    clock_gettime(CLOCK_REALTIME, &real);\n    Time::steady_tp now    = Time::steadyNow();\n    Time::system_tp nowSys = Time::systemNow();\n    s_ns            stdSteady, stdReal;\n    stdSteady = Time::secNsec(now);\n    stdReal   = Time::secNsec(nowSys);\n\n    // timespec difference, REAL - MONO\n    s_ns diff;\n    diff.first = real.tv_sec - mono.tv_sec;\n    if (real.tv_nsec >= mono.tv_nsec)\n        diff.second = real.tv_nsec - mono.tv_nsec;\n    else {\n        diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec;\n        diff.first -= 1;\n    }\n\n    // STD difference, REAL - MONO\n    s_ns diff2 = timediff(stdReal, stdSteady);\n\n    s_ns diffFinal;\n    s_ns tpTime = secNsec(tp);\n\n    if (diff.first >= diff2.first || (diff.first == diff2.first && diff.second >= diff2.second))\n        diffFinal = timediff(diff, diff2);\n    else\n        diffFinal = timediff(diff2, diff);\n\n    auto sum = timeadd(tpTime, diffFinal);\n    return timespec{.tv_sec = sum.first, .tv_nsec = sum.second};\n}\n\nTime::steady_dur Time::till(const timespec& ts) {\n    timespec mono{};\n    clock_gettime(CLOCK_MONOTONIC, &mono);\n    const auto delay = (ts.tv_sec - mono.tv_sec) * 1000000000 + (ts.tv_nsec - mono.tv_nsec);\n    return std::chrono::nanoseconds(delay);\n}\n"
  },
  {
    "path": "src/helpers/time/Time.hpp",
    "content": "#pragma once\n\n#include <chrono>\n#include <cstdint>\n#include <utility>\n#include <ctime>\n\n//NOLINTNEXTLINE\nnamespace Time {\n    using steady_tp  = std::chrono::steady_clock::time_point;\n    using system_tp  = std::chrono::system_clock::time_point;\n    using steady_dur = std::chrono::steady_clock::duration;\n    using system_dur = std::chrono::system_clock::duration;\n\n    steady_tp                     steadyNow();\n    system_tp                     systemNow();\n\n    steady_tp                     fromTimespec(const timespec*);\n    struct timespec               toTimespec(const steady_tp& tp);\n    steady_dur                    till(const timespec& ts);\n\n    uint64_t                      millis(const steady_tp& tp);\n    uint64_t                      millis(const system_tp& tp);\n    std::pair<uint64_t, uint64_t> secNsec(const steady_tp& tp);\n    std::pair<uint64_t, uint64_t> secNsec(const system_tp& tp);\n};"
  },
  {
    "path": "src/helpers/time/Timer.cpp",
    "content": "#include \"Timer.hpp\"\n\n#define chr std::chrono\n\nvoid CTimer::reset() {\n    m_lastReset = Time::steadyNow();\n}\n\nTime::steady_dur CTimer::getDuration() const {\n    return Time::steadyNow() - m_lastReset;\n}\n\nfloat CTimer::getMillis() const {\n    return chr::duration_cast<chr::microseconds>(getDuration()).count() / 1000.F;\n}\n\nfloat CTimer::getSeconds() const {\n    return chr::duration_cast<chr::milliseconds>(getDuration()).count() / 1000.F;\n}\n\nconst Time::steady_tp& CTimer::chrono() const {\n    return m_lastReset;\n}\n"
  },
  {
    "path": "src/helpers/time/Timer.hpp",
    "content": "#pragma once\n\n#include \"Time.hpp\"\n\nclass CTimer {\n  public:\n    void                   reset();\n    float                  getSeconds() const;\n    float                  getMillis() const;\n    const Time::steady_tp& chrono() const;\n\n  private:\n    Time::steady_tp  m_lastReset;\n\n    Time::steady_dur getDuration() const;\n};\n"
  },
  {
    "path": "src/helpers/varlist/VarList.hpp",
    "content": "#pragma once\n\n#include <hyprutils/string/VarList.hpp>\n#include <hyprutils/string/VarList2.hpp>\n\n//NOLINTNEXTLINE\nusing namespace Hyprutils::String;\n"
  },
  {
    "path": "src/hyprerror/HyprError.cpp",
    "content": "#include <pango/pangocairo.h>\n#include \"HyprError.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../render/pass/TexPassElement.hpp\"\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../event/EventBus.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\nusing namespace Hyprutils::Animation;\n\nCHyprError::CHyprError() {\n    g_pAnimationManager->createAnimation(0.f, m_fadeOpacity, g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"), AVARDAMAGE_NONE);\n\n    static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) {\n        if (!m_isCreated)\n            return;\n\n        g_pHyprRenderer->damageMonitor(Desktop::focusState()->monitor());\n        m_monitorChanged = true;\n    });\n\n    static auto P2 = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) {\n        if (!m_isCreated)\n            return;\n\n        if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged)\n            g_pHyprRenderer->damageBox(m_damageBox);\n    });\n}\n\nvoid CHyprError::queueCreate(std::string message, const CHyprColor& color) {\n    m_queued      = message;\n    m_queuedColor = color;\n}\n\nvoid CHyprError::createQueued() {\n    if (m_isCreated && m_texture)\n        m_texture.reset();\n\n    m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"));\n\n    m_fadeOpacity->setValueAndWarp(0.f);\n    *m_fadeOpacity = 1.f;\n\n    const auto PMONITOR = g_pCompositor->m_monitors.front();\n\n    const auto SCALE = PMONITOR->m_scale;\n\n    const auto FONTSIZE = std::clamp(sc<int>(10.f * ((PMONITOR->m_pixelSize.x * SCALE) / 1920.f)), 8, 40);\n\n    const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y);\n\n    const auto CAIRO = cairo_create(CAIROSURFACE);\n\n    // clear the pixmap\n    cairo_save(CAIRO);\n    cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);\n    cairo_paint(CAIRO);\n    cairo_restore(CAIRO);\n\n    const auto   LINECOUNT    = Hyprlang::INT{1} + std::ranges::count(m_queued, '\\n');\n    static auto  LINELIMIT    = CConfigValue<Hyprlang::INT>(\"debug:error_limit\");\n    static auto  BAR_POSITION = CConfigValue<Hyprlang::INT>(\"debug:error_position\");\n\n    const bool   TOPBAR = *BAR_POSITION == 0;\n\n    const auto   VISLINECOUNT = std::min(LINECOUNT, *LINELIMIT);\n    const auto   EXTRALINES   = (VISLINECOUNT < LINECOUNT) ? 1 : 0;\n\n    const double DEGREES = M_PI / 180.0;\n\n    const double PAD = 10 * SCALE;\n\n    const double WIDTH  = PMONITOR->m_pixelSize.x - PAD * 2;\n    const double HEIGHT = (FONTSIZE + 2 * (FONTSIZE / 10.0)) * (VISLINECOUNT + EXTRALINES) + 3;\n    const double RADIUS = PAD > HEIGHT / 2 ? HEIGHT / 2 - 1 : PAD;\n    const double X      = PAD;\n    const double Y      = TOPBAR ? PAD : PMONITOR->m_pixelSize.y - HEIGHT - PAD;\n\n    m_damageBox = {sc<int>(PMONITOR->m_position.x), sc<int>(PMONITOR->m_position.y + (TOPBAR ? 0 : PMONITOR->m_pixelSize.y - (HEIGHT + PAD * 2))), sc<int>(PMONITOR->m_pixelSize.x),\n                   sc<int>(HEIGHT + PAD * 2)};\n\n    cairo_new_sub_path(CAIRO);\n    cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + RADIUS, RADIUS, -90 * DEGREES, 0 * DEGREES);\n    cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + HEIGHT - RADIUS, RADIUS, 0 * DEGREES, 90 * DEGREES);\n    cairo_arc(CAIRO, X + RADIUS, Y + HEIGHT - RADIUS, RADIUS, 90 * DEGREES, 180 * DEGREES);\n    cairo_arc(CAIRO, X + RADIUS, Y + RADIUS, RADIUS, 180 * DEGREES, 270 * DEGREES);\n    cairo_close_path(CAIRO);\n\n    cairo_set_source_rgba(CAIRO, 0.06, 0.06, 0.06, 1.0);\n    cairo_fill_preserve(CAIRO);\n    cairo_set_source_rgba(CAIRO, m_queuedColor.r, m_queuedColor.g, m_queuedColor.b, m_queuedColor.a);\n    cairo_set_line_width(CAIRO, 2);\n    cairo_stroke(CAIRO);\n\n    // draw the text with a common font\n    const CHyprColor textColor = CHyprColor(0.9, 0.9, 0.9, 1.0);\n    cairo_set_source_rgba(CAIRO, textColor.r, textColor.g, textColor.b, textColor.a);\n\n    static auto           fontFamily = CConfigValue<std::string>(\"misc:font_family\");\n    PangoLayout*          layoutText = pango_cairo_create_layout(CAIRO);\n    PangoFontDescription* pangoFD    = pango_font_description_new();\n\n    pango_font_description_set_family(pangoFD, (*fontFamily).c_str());\n    pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE);\n    pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL);\n    pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL);\n    pango_layout_set_font_description(layoutText, pangoFD);\n    pango_layout_set_width(layoutText, (WIDTH - 2 * (1 + RADIUS)) * PANGO_SCALE);\n    pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END);\n\n    float yoffset     = TOPBAR ? 0 : Y - PAD;\n    int   renderedcnt = 0;\n    while (!m_queued.empty() && renderedcnt < VISLINECOUNT) {\n        std::string current = m_queued.substr(0, m_queued.find('\\n'));\n        if (const auto NEWLPOS = m_queued.find('\\n'); NEWLPOS != std::string::npos)\n            m_queued = m_queued.substr(NEWLPOS + 1);\n        else\n            m_queued = \"\";\n        cairo_move_to(CAIRO, PAD + 1 + RADIUS, yoffset + PAD + 1);\n        pango_layout_set_text(layoutText, current.c_str(), -1);\n        pango_cairo_show_layout(CAIRO, layoutText);\n        yoffset += FONTSIZE + (FONTSIZE / 10.f);\n        renderedcnt++;\n    }\n    if (VISLINECOUNT < LINECOUNT) {\n        std::string moreString = std::format(\"({} more...)\", LINECOUNT - VISLINECOUNT);\n        cairo_move_to(CAIRO, PAD + 1 + RADIUS, yoffset + PAD + 1);\n        pango_layout_set_text(layoutText, moreString.c_str(), -1);\n        pango_cairo_show_layout(CAIRO, layoutText);\n    }\n\n    m_lastHeight = HEIGHT;\n\n    pango_font_description_free(pangoFD);\n    g_object_unref(layoutText);\n\n    cairo_surface_flush(CAIROSURFACE);\n\n    // copy the data to an OpenGL texture we have\n    const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);\n    auto       tex  = texture();\n    tex->allocate(PMONITOR->m_pixelSize);\n    tex->bind();\n    tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n    tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n    tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);\n    tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);\n\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);\n\n    // delete cairo\n    cairo_destroy(CAIRO);\n    cairo_surface_destroy(CAIROSURFACE);\n\n    m_isCreated   = true;\n    m_queued      = \"\";\n    m_queuedColor = CHyprColor();\n\n    g_pHyprRenderer->damageMonitor(PMONITOR);\n\n    for (const auto& m : g_pCompositor->m_monitors) {\n        m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR);\n    }\n\n    const auto RESERVED = (HEIGHT + PAD) / SCALE;\n    PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR, Vector2D{0.0, TOPBAR ? RESERVED : 0.0}, Vector2D{0.0, !TOPBAR ? RESERVED : 0.0});\n\n    for (const auto& m : g_pCompositor->m_monitors) {\n        g_pHyprRenderer->arrangeLayersForMonitor(m->m_id);\n    }\n}\n\nvoid CHyprError::draw() {\n    if (!m_isCreated || !m_queued.empty()) {\n        if (!m_queued.empty())\n            createQueued();\n        return;\n    }\n\n    if (m_queuedDestroy) {\n        if (!m_fadeOpacity->isBeingAnimated()) {\n            if (m_fadeOpacity->value() == 0.f) {\n                m_queuedDestroy = false;\n                if (m_texture)\n                    m_texture.reset();\n                m_isCreated = false;\n                m_queued    = \"\";\n\n                for (auto& m : g_pCompositor->m_monitors) {\n                    g_pHyprRenderer->arrangeLayersForMonitor(m->m_id);\n                    m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR);\n                }\n\n                return;\n            } else {\n                m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadeOut\"));\n                *m_fadeOpacity = 0.f;\n            }\n        }\n    }\n\n    const auto  PMONITOR = g_pHyprRenderer->m_renderData.pMonitor;\n\n    CBox        monbox = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y};\n\n    static auto BAR_POSITION = CConfigValue<Hyprlang::INT>(\"debug:error_position\");\n    m_damageBox.x            = sc<int>(PMONITOR->m_position.x);\n    m_damageBox.y            = sc<int>(PMONITOR->m_position.y + (*BAR_POSITION == 0 ? 0 : PMONITOR->m_pixelSize.y - m_damageBox.height));\n\n    if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged)\n        g_pHyprRenderer->damageBox(m_damageBox);\n\n    m_monitorChanged = false;\n\n    CTexPassElement::SRenderData data;\n    data.tex = texture();\n    data.box = monbox;\n    data.a   = m_fadeOpacity->value();\n\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n\nvoid CHyprError::destroy() {\n    if (m_isCreated)\n        m_queuedDestroy = true;\n    else\n        m_queued = \"\";\n}\n\nbool CHyprError::active() {\n    return m_isCreated;\n}\n\nfloat CHyprError::height() {\n    return m_lastHeight;\n}\n\nSP<ITexture> CHyprError::texture() {\n    if (!m_texture)\n        m_texture = g_pHyprRenderer->createTexture();\n    return m_texture;\n}"
  },
  {
    "path": "src/hyprerror/HyprError.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../render/Texture.hpp\"\n#include \"../helpers/AnimatedVariable.hpp\"\n\n#include <cairo/cairo.h>\n\nclass CHyprError {\n  public:\n    CHyprError();\n    ~CHyprError() = default;\n\n    void         queueCreate(std::string message, const CHyprColor& color);\n    void         draw();\n    void         destroy();\n\n    bool         active();\n    float        height(); // logical\n\n    SP<ITexture> texture();\n\n  private:\n    void              createQueued();\n    std::string       m_queued = \"\";\n    CHyprColor        m_queuedColor;\n    bool              m_queuedDestroy = false;\n    bool              m_isCreated     = false;\n    SP<ITexture>      m_texture;\n    PHLANIMVAR<float> m_fadeOpacity;\n    CBox              m_damageBox  = {0, 0, 0, 0};\n    float             m_lastHeight = 0.F;\n\n    bool              m_monitorChanged = false;\n};\n\ninline UP<CHyprError> g_pHyprError; // This is a full-screen error. Treat it with respect, and there can only be one at a time.\n"
  },
  {
    "path": "src/i18n/Engine.cpp",
    "content": "#include \"Engine.hpp\"\n\n#include <hyprutils/i18n/I18nEngine.hpp>\n#include \"../config/ConfigValue.hpp\"\n\nusing namespace I18n;\nusing namespace Hyprutils::I18n;\n\nstatic SP<Hyprutils::I18n::CI18nEngine> huEngine;\nstatic std::string                      localeStr;\n\n//\nSP<I18n::CI18nEngine> I18n::i18nEngine() {\n    static SP<I18n::CI18nEngine> engine = makeShared<I18n::CI18nEngine>();\n    return engine;\n}\n\nI18n::CI18nEngine::CI18nEngine() {\n    huEngine = makeShared<Hyprutils::I18n::CI18nEngine>();\n    huEngine->setFallbackLocale(\"en_US\");\n    localeStr = huEngine->getSystemLocale().locale();\n\n    // be_BY (Belarusian)\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_ANR_TITLE, \"Праграма не адказвае\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_ANR_CONTENT, \"Праграма {title} - {class} не адказвае.\\nШто хочаце з ёй зрабіць?\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_ANR_OPTION_TERMINATE, \"Прымусова спыніць\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_ANR_OPTION_WAIT, \"Пачакаць\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_ANR_PROP_UNKNOWN, \"(невядома)\");\n\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Праграма <b>{app}</b> запытвае невядомы дазвол.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Праграма <b>{app}</b> спрабуе здымаць экран.\\n\\nЦі хочаце дазволіць?\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Праграма <b>{app}</b> спрабуе загрузіць плагін: <b>{plugin}</b>.\\n\\nХочаце дазволіць?\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Выяўленая новая клавіятура: <b>{keyboard}</b>.\\n\\nХочаце дазволіць яе выкарыстанне?\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(невядома)\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_TITLE, \"Запыт дазволу\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Падказка: вы можаце задаць пастаянныя правілы для гэтага ў файле канфігурацыі Hyprland.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_ALLOW, \"Дазволіць\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Дазволіць і запомніць\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Дазволіць аднойчы\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_DENY, \"Забараніць\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Невядомая праграма (Ідэнтыфікатар кліента wayland {wayland_id})\");\n\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Выглядае, што вашая пераменная асяроддзя XDG_CURRENT_DESKTOP зададзеная звонку, цяперашняе значэнне: {value}.\\nГэта можа выклікаць праблемы, калі \"\n                            \"гэта не зроблена наўмысна.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"У вашай сістэме не ўсталяваны hyprland-guiutils, што выкарыстоўваецца для некаторых дыялогавых вокнаў. Разгледзьце ўсталёўку пакета.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland не змог загрузіць {count} важны рэсурс, вінавацьце ў гэтым адказнага за зборку пакетаў для свайго дыстрыбутыва!\";\n        return \"Hyprland не змог загрузіць {count} важных рэсурсаў, вінавацьце ў гэтым адказнага за зборку пакетаў для свайго дыстрыбутыва!\";\n    });\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Макет манітораў наладжаны некарэктна. Манітор {name} накладаецца на іншы(я) манітор(ы).\\nДля падрабязнасцей звярніцеся да Wiki (Старонка Monitors). \"\n                            \"Гэта <b>абавязкова</b> створыць праблемы.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Манітор {name} не змог наладзіць ніводны з запатрабаваных рэжымаў, аварыйна ўжыты рэжым {mode}.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Няверна зададзены маштаб для манітора {name}: {scale}, ужываецца прапанаваны маштаб: {fixed_scale}\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Не атрымалася загрузіць плагін {name}: {error}\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Не атрымалася перазагрузіць шэйдар CM, аварыйна ўжываецца rgba/rgbx.\");\n    huEngine->registerEntry(\"be_BY\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Манітор {name}: пашыраны каляровы дыяпазон уключаны, але экран не ў рэжыме 10-біт.\");\n\n    // bn_BD (Bengali)\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_ANR_TITLE, \"অ্যাপ্লিকেশন সাড়া দিচ্ছে না\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_ANR_CONTENT, \"অ্যাপ্লিকেশন {title} - {class} সাড়া দিচ্ছে না।\\nআপনি এটি নিয়ে কি করতে চান?\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_ANR_OPTION_TERMINATE, \"বন্ধ করুন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_ANR_OPTION_WAIT, \"অপেক্ষা করুন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_ANR_PROP_UNKNOWN, \"(অজানা)\");\n\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"একটি অ্যাপ্লিকেশন <b>{app}</b> একটি অজানা অনুমতির অনুরোধ করছে।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"একটি অ্যাপ্লিকেশন <b>{app}</b> আপনার স্ক্রিন রেকর্ড করার চেষ্টা করছে।\\n\\nআপনি কি এটি অনুমতি দিতে চান?\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n                            \"একটি অ্যাপ্লিকেশন <b>{app}</b> একটি প্লাগইন লোড করার চেষ্টা করছে: <b>{plugin}</b>।\\n\\nআপনি কি এটি অনুমতি দিতে চান?\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"একটি নতুন কীবোর্ড সনাক্ত করা হয়েছে: <b>{keyboard}</b>।\\n\\nআপনি কি এটি কাজ করতে অনুমতি দিতে চান?\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(অজানা)\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_TITLE, \"অনুমতির অনুরোধ\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"টিপ: আপনি Hyprland কনফিগারেশন ফাইলে এর জন্য স্থায়ী নিয়ম সেট করতে পারেন।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_ALLOW, \"অনুমতি দিন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"অনুমতি দিন এবং মনে রাখুন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"একবার অনুমতি দিন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_DENY, \"প্রত্যাখ্যান করুন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"অজানা অ্যাপ্লিকেশন (wayland ক্লায়েন্ট ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"bn_BD\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"আপনার XDG_CURRENT_DESKTOP পরিবেশ পরিবর্তনশীল বাহ্যিকভাবে পরিচালিত হচ্ছে বলে মনে হচ্ছে, বর্তমান মান: {value}।\\nএটি সমস্যা সৃষ্টি করতে পারে যদি না এটি ইচ্ছাকৃত হয়।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_NO_GUIUTILS, \"আপনার সিস্টেমে hyprland-guiutils ইনস্টল নেই যা কিছু ডায়ালগের জন্য ব্যবহৃত হয়। এটি ইনস্টল করার কথা বিবেচনা করুন।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland {count}টি প্রয়োজনীয় সম্পদ লোড করতে ব্যর্থ হয়েছে, খারাপ প্যাকেজিং কাজের জন্য আপনার ডিস্ট্রো প্যাকেজারদের দোষ দিন!\";\n        return \"Hyprland {count}টি প্রয়োজনীয় সম্পদ লোড করতে ব্যর্থ হয়েছে, খারাপ প্যাকেজিং কাজের জন্য আপনার ডিস্ট্রো প্যাকেজারদের দোষ দিন!\";\n    });\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"আপনার মনিটর লেআউট ভুলভাবে কনফিগার করা হয়েছে। মনিটর {name} লেআউটে অন্য মনিটর(গুলি) এর সাথে ওভারল্যাপ করছে।\\nবিস্তারিত জানতে wiki (Monitors page) দেখুন। \"\n                            \"এটি <b>অবশ্যই</b> সমস্যা সৃষ্টি করবে।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"মনিটর {name} কোনো অনুরোধকৃত মোড সেট করতে পারেনি, মোড {mode} এ ফিরে যাচ্ছে।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"মনিটর {name} এর জন্য অবৈধ স্কেল পাঠানো হয়েছে: {scale}, প্রস্তাবিত স্কেল ব্যবহার করা হচ্ছে: {fixed_scale}\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"প্লাগইন {name} লোড করতে ব্যর্থ: {error}\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM শেডার পুনরায় লোড করতে ব্যর্থ, rgba/rgbx এ ফিরে যাচ্ছে।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"মনিটর {name}: ওয়াইড কালার গ্যামুট সক্রিয় কিন্তু স্ক্রিন 10-বিট মোডে নেই।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_NOTIF_NO_WATCHDOG, \"Hyprland start-hyprland ছাড়া চালু করা হয়েছে। এটি অত্যন্ত সুপারিশকৃত নয় যদি না আপনি ডিবাগিং পরিবেশে থাকেন।\");\n\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_SAFE_MODE_TITLE, \"নিরাপদ মোড\");\n    huEngine->registerEntry(\n        \"bn_BD\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n        \"Hyprland নিরাপদ মোডে চালু করা হয়েছে, যার মানে আপনার শেষ সেশন ক্র্যাশ হয়েছিল।\\nনিরাপদ মোড আপনার কনফিগ লোড হওয়া থেকে প্রতিরোধ করে। আপনি \"\n        \"এই পরিবেশে সমস্যা সমাধান করতে পারেন, অথবা নিচের বাটন দিয়ে আপনার কনফিগ লোড করতে পারেন।\\nডিফল্ট কীবাইন্ড প্রযোজ্য: kitty এর জন্য SUPER+Q, মৌলিক রানারের জন্য SUPER+R, \"\n        \"প্রস্থান করতে SUPER+M।\\nHyprland পুনরায় চালু করলে আবার স্বাভাবিক মোডে চালু হবে।\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"কনফিগ লোড করুন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"ক্র্যাশ রিপোর্ট ডিরেক্টরি খুলুন\");\n    huEngine->registerEntry(\"bn_BD\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"ঠিক আছে, এটি বন্ধ করুন\");\n\n    // da_DK (Danish)\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_ANR_TITLE, \"Applikationen Svarer Ikke\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_ANR_CONTENT, \"En applikation {title} - {class} svarer ikke.\\nHvad vil du gøre ved det?\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_ANR_OPTION_TERMINATE, \"Luk\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_ANR_OPTION_WAIT, \"Vent\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_ANR_PROP_UNKNOWN, \"(ukendt)\");\n\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"En applikation <b>{app}</b> forespørger en ukendt rettighed.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"En applikation <b>{app}</b> forsøger at optage din skærm.\\n\\nVil du tillade dette?\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"En applikation <b>{app}</b> forsøger at indlæse et plugin: <b>{plugin}</b>.\\n\\nVil du tillade dette?\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Et nyt tastatur er fundet: <b>{keyboard}</b>.\\n\\nVil du tillade den at fungere?\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(ukendt)\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_TITLE, \"Anmodning om tilladelse\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Tip: Du kan indstille vedvarende regler for disse i Hyprland-konfigurationsfilen.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_ALLOW, \"Tillad\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Tillad og husk\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Tillad én gang\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_DENY, \"Nægt\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Ukendt applikation (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"da_DK\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Dit XDG_CURRENT_DESKTOP miljø ser ud til at være administreret externt, og den nuværende værdi er {value}.\\nDette kan forårsage problemer, medmindre det er bevidst.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Dit system har ikke hyprland-guiutils installeret. Dette er en runtime-afhængighed for nogle dialoger. Overvej at installere den.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland kunne ikke indlæse {count} essentiale aktiver, skyd skylden på din distributions pakker for et dårligt stykke arbejde af pakningen!\";\n        return \"Hyprland kunne ikke indlæse {count} essentiale aktiver, skyd skylden på din distributions pakker for et dårligt stykke arbejde af pakningen!\";\n    });\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Dit skærmlayout har en ukorrekt opsætning. Skærm {name} overlapper med andre skærm(e) i layoutet.\\nLæs venligst wiki'en (Monitors page) for \"\n                            \"mere. Dette <b>vil</b> skabe problemer.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Skærm {name} kunne ikke indlæse nogen af de ønskede tilstande, vender tilbage til tilstand {mode}.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Ugyldig skalering sendt til skærm {name}: {scale}, bruger foreslået skalering: {fixed_scale}\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Kunne ikke indlæse plugin {name}: {error}\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Genindlæsning af CM-shader mislykkedes, går tilbage til rgba/rgbx.\");\n    huEngine->registerEntry(\"da_DK\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Skærm {name}: wide color gamut er aktiveret men skærmen er ikke i 10-bit tilstand.\");\n\n    // en_US (English)\n    huEngine->registerEntry(\"en_US\", TXT_KEY_ANR_TITLE, \"Application Not Responding\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_ANR_CONTENT, \"An application {title} - {class} is not responding.\\nWhat do you want to do with it?\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_ANR_OPTION_TERMINATE, \"Terminate\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_ANR_OPTION_WAIT, \"Wait\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_ANR_PROP_UNKNOWN, \"(unknown)\");\n\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"An application <b>{app}</b> is requesting an unknown permission.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"An application <b>{app}</b> is trying to capture your screen.\\n\\nDo you want to allow it to?\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS,\n                            \"An application <b>{app}</b> is trying to capture your cursor position.\\n\\nDo you want to allow it to?\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"An application <b>{app}</b> is trying to load a plugin: <b>{plugin}</b>.\\n\\nDo you want to allow it to?\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"A new keyboard has been detected: <b>{keyboard}</b>.\\n\\nDo you want to allow it to operate?\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(unknown)\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_TITLE, \"Permission request\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Hint: you can set persistent rules for these in the Hyprland config file.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_ALLOW, \"Allow\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Allow and remember\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Allow once\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_DENY, \"Deny\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Unknown application (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"en_US\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {value}.\\nThis might cause issues unless it's intentional.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland failed to load {count} essential asset, blame your distro's packager for doing a bad job at packaging!\";\n        return \"Hyprland failed to load {count} essential assets, blame your distro's packager for doing a bad job at packaging!\";\n    });\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Your monitor layout is set up incorrectly. Monitor {name} overlaps with other monitor(s) in the layout.\\nPlease see the wiki (Monitors page) for \"\n                            \"more. This <b>will</b> cause issues.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitor {name} failed to set any requested modes, falling back to mode {mode}.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Invalid scale passed to monitor {name}: {scale}, using suggested scale: {fixed_scale}\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Failed to load plugin {name}: {error}\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM shader reload failed, falling back to rgba/rgbx.\");\n    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.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_NOTIF_NO_WATCHDOG,\n                            \"Hyprland was started without start-hyprland. This is highly not recommended unless you are in a debugging environment.\");\n\n    huEngine->registerEntry(\"en_US\", TXT_KEY_SAFE_MODE_TITLE, \"Safe Mode\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"Hyprland has been launched in safe mode, which means your last session crashed.\\nSafe mode prevents your config from being loaded. You can \"\n                            \"troubleshoot in this environment, or load your config with the button below.\\nDefault keybinds apply: SUPER+Q for kitty, SUPER+R for a basic runner, \"\n                            \"SUPER+M to exit.\\nRestarting \"\n                            \"Hyprland will launch in normal mode again.\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"Load config\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"Open crash report directory\");\n    huEngine->registerEntry(\"en_US\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"Ok, close this\");\n\n    // as_IN (Assamese)\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_ANR_TITLE, \"এপ্লিকেচনে উত্তৰ দিয়া নাই\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_ANR_CONTENT, \"এপ্লিকেচন {title} - {class}-এ উত্তৰ দিয়া নাই।\\nআপুনি এয়াৰ লগত কি কৰিব বিচাৰে?\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_ANR_OPTION_TERMINATE, \"সমাপ্ত কৰক\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_ANR_OPTION_WAIT, \"অপেক্ষা কৰক\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_ANR_PROP_UNKNOWN, \"(অজ্ঞাত)\");\n\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"এপ্লিকেচন <b>{app}</b>-এ এটা অজ্ঞাত অনুমতি বিচাৰিছে।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"এটা এপ্লিকেচন <b>{app}</b>-এ আপোনাৰ স্ক্ৰীণ কেপচাৰ কৰিবলৈ চেষ্টা কৰিছে।\\n\\nআপুনি ইয়াক অনুমতি দিব বিচাৰেনে?\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n                            \"এপ্লিকেচন <b>{app}</b>-এ এটা প্লাগিন লোড কৰিবলৈ চেষ্টা কৰিছে: <b>{plugin}</b>।\\n\\nআপুনি ইয়াক অনুমতি দিব বিচাৰেনে?\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"এটা নতুন কিবৰ্ড ধৰা পৰিছে: <b>{keyboard}</b>।\\n\\nআপুনি ইয়াক চলাবলৈ অনুমতি দিব বিচাৰেনে?\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(অজ্ঞাত)\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_TITLE, \"অনুমতিৰ অনুৰোধ\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"ইঙ্গিত: আপুনি হাইপাৰলেণ্ড কনফিগ ফাইলত এইবোৰৰ বাবে স্থায়ী নিয়ম স্থাপন কৰিব পাৰে।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_ALLOW, \"অনুমতি দিয়ক\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"অনুমতি দি মনত ৰাখক\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"এবাৰ অনুমতি দিয়ক\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_DENY, \"অস্বীকাৰ কৰক\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"অজ্ঞাত এপ্লিকেচন (ৱেইলেণ্ড ক্লায়েণ্ট আইডি {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"as_IN\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"আপোনাৰ XDG_CURRENT_DESKTOP পৰিৱেশটো বাহ্যিকভাৱে পৰিচালিত হোৱা যেন লাগিছে, আৰু বৰ্তমানৰ মান হৈছে {value}।\\nযদি ই ইচ্ছাকৃতভাৱে নহয়, তেনে হলে সমস্যাৰ সৃষ্টি হ'ব পাৰে।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"আপোনাৰ চিষ্টেমত hyprland-guiutils ইনষ্টল কৰা নাই। কিছুমান ডাইলগৰ বাবে ই এটা ৰানটাইম নিৰ্ভৰশীলতা। ইয়াক ইনষ্টল কৰাৰ কথা চিন্তা কৰক।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_FAILED_ASSETS,\n                            \"হাইপাৰলেণ্ড {count}-টা প্ৰয়োজনীয় সম্পদ লোড কৰাত অসফল হৈছে, বেয়া পেকজিং কৰাৰ বাবে আপোনাৰ ডিষ্ট্ৰ'ৰ পেকেজাৰক দোষাৰোপ কৰক!\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"আপোনাৰ মনিটৰৰ লেআউট ভুলকৈ ছেট কৰা হৈছে। মনিটৰ {name} লেআউটত আন মনিটৰ(সমূহ)ৰ সৈতে ওপৰা-উপৰি হৈ আছে।\\nঅধিক তথ্যৰ বাবে অনুগ্ৰহ কৰি ৱিকি (মনিটৰ পৃষ্ঠা) চাওক। ই \"\n                            \"<b>সমস্যাৰ</b> সৃষ্টি কৰিব।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"মনিটৰ {name}-এ কোনো অনুৰোধ কৰা মোড ছেট কৰাত অসফল হৈছে, মোড {mode}-লৈ ঘূৰি আহিছে।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"মনিটৰ {name}: {scale}-লৈ অবৈধ মাপন দিয়া হৈছে, পৰামৰ্শ দিয়া মাপন ব্যৱহাৰ কৰা যাব: {fixed_scale}\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"প্লাগিন {name} লোড কৰাত অসফল হৈছে: {error}\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM শ্বেডাৰ ৰিলোড কৰাত অসফল হৈছে, rgba/rgbx-লৈ ঘূৰি আহিছে।\");\n    huEngine->registerEntry(\"as_IN\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"প্ৰসাৰিত ৰঙৰ বৰ্গ সক্ষম কৰা হৈছে কিন্তু ডিচপ্লে 10-বিট মোডত নাই।\");\n\n    // de_DE (German)\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_ANR_TITLE, \"Anwendung Reagiert Nicht\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_ANR_CONTENT, \"Eine Anwendung {title} - {class} reagiert nicht.\\nWas möchten Sie damit tun?\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_ANR_OPTION_TERMINATE, \"Beenden\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_ANR_OPTION_WAIT, \"Warten\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_ANR_PROP_UNKNOWN, \"(unbekannt)\");\n\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Eine Anwendung <b>{app}</b> fordert eine unbekannte Berechtigung an.\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Eine Anwendung <b>{app}</b> versucht Ihren Bildschrim aufzunehmen.\\n\\nMöchten Sie dies erlauben?\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Eine Anwendung <b>{app}</b> versucht ein Plugin zu laden: <b>{plugin}</b>.\\n\\nMöchten Sie dies erlauben?\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Eine neue Tastatur wurde erkannt: <b>{keyboard}</b>.\\n\\nMöchten Sie diese in Betrieb nehmen?\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(unbekannt)\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_TITLE, \"Berechtigungsanfrage\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Tip: Sie können dafür permanente Regeln in der Hyprland-Konfigurationsdatei festlegen.\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_ALLOW, \"Erlauben\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Erlauben und merken\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Einmal erlauben\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_DENY, \"Verweigern\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Unbekannte Anwendung (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"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 \"\n                            \"nicht absichtlich so ist.\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Ihr System hat hyprland-guiutils nicht installiert. Dies ist eine Laufzeitabhängigkeit für einige Dialoge. Es ist empfohlen diese zu installieren.\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland konnte {count} essentielle Ressource nicht laden, geben Sie dem Packager ihrer Distribution die Schuld für ein schlechtes Package!\";\n        return \"Hyprland konnte {count} essentielle Ressroucen nicht laden, geben Sie dem Packager ihrer Distribution die Schuld für ein schlechtes Package!\";\n    });\n    huEngine->registerEntry(\n        \"de_DE\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"Ihr Bildschirmlayout ist fehlerhaft aufgesetzt. Der Bildschirm {name} überlappt mit anderen Bildschirm(en) im Layout.\\nBitte siehe im Wiki (Monitors Seite) für \"\n        \"mehr Informationen. Dies <b>wird</b> zu Problemen führen.\");\n    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.\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Ungültiger Skalierungsfaktor {scale} für Bildschirm {name}, es wird der empfohlene Faktor {fixed_scale} verwendet.\");\n    huEngine->registerEntry(\"de_DE\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Plugin {name} konnte nicht geladen werden: {error}\");\n    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.\");\n    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.\");\n\n    // de_CH (Swiss German)\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_ANR_TITLE, \"Aawändig Reagiert Ned\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_ANR_CONTENT, \"En Aawändig {title} - {class} reagiert ned.\\nWas wend Sie demet mache?\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_ANR_OPTION_TERMINATE, \"Beände\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_ANR_OPTION_WAIT, \"Warte\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_ANR_PROP_UNKNOWN, \"(onbekannt)\");\n\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"En Aawändig <b>{app}</b> fordert en onbekannti Berächtigong aa.\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"En Aawändig <b>{app}</b> versuecht Ehre Beldscherm uufznäh.\\n\\nWend Sie das erlaube?\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"En Aawändig <b>{app}</b> versuecht es Plugin z'lade: <b>{plugin}</b>.\\n\\nWend Sie das erlaube?\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"En neui Tastatur esch erkönne worde: <b>{keyboard}</b>.\\n\\nWend sie die in Betreb nä?\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(onbekannt)\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_TITLE, \"Berächtigongsaafrog\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Tip: Sie chönd permanenti Regle deför i ehrere Hyprland-Konfigurationsdatei festlegge.\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_ALLOW, \"Erlaube\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Erlaube ond merke\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Einisch erlaube\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_DENY, \"Verweigere\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Onbekannti Aawändig (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"de_CH\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"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.\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Ehres System hed hyprland-guiutils ned installiert. Das esch en Laufziitabhängigkeit för es paar Dialog. Es werd empfohle sie z'installiere.\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_NOTIF_FAILED_ASSETS,\n                            \"Hyprland hed {count} essentielli Ressource ned chönne lade, gäbed Sie im Packager vo ehrere Distribution schold för es schlächts Package!\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Ehres Beldschermlayout esch fählerhaft uufgsetzt. De Beldscherm {name} öberlappt met andere Beldscherm(e) im Layout.\\nBitte lueged sie im Wiki \"\n                            \"(Monitors Siite) för meh Informatione. Das <b>werd</b> zo Problem füehre.\");\n    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.\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Ongöltige Skalierigsfaktor {scale} för de Beldscherm {name}, es werd de empfohleni Faktor {fixed_scale} verwändet.\");\n    huEngine->registerEntry(\"de_CH\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"S Plugin {name} hed ned chönne glade wärde: {error}\");\n    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.\");\n    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.\");\n\n    // pt_BR (Brazilian Portuguese)\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_ANR_TITLE, \"O aplicativo não está respondendo\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_ANR_CONTENT, \"O aplicativo {title} - {class} não está respondendo.\\nO que você deseja fazer?\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_ANR_OPTION_TERMINATE, \"Encerrar\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_ANR_OPTION_WAIT, \"Esperar\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_ANR_PROP_UNKNOWN, \"(Desconhecido)\");\n\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"O aplicativo <b>{app}</b> está pedindo uma permissão desconhecida.\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"O aplicativo <b>{app}</b> está tentando capturar sua tela.\\n\\nVocê deseja permitir?\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"O aplicativo <b>{app}</b> está tentando carregar um plugin: <b>{plugin}</b>.\\n\\nVocê deseja permitir?\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Um novo teclado foi detectado: <b>{keyboard}</b>.\\n\\nVocê deseja permitir seu uso?\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(Desconhecido)\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_TITLE, \"Solicitação de permissão\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_PERSISTENCE_HINT,\n                            \"Dica: você pode definir regras persistentes para essas permissões no arquivo de configuração do Hyprland\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_ALLOW, \"Permitir\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Permitir e lembrar\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Permitir uma vez\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_DENY, \"Negar\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Aplicativo desconhecido (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Seu XDG_CURRENT_DESKTOP parece estar sendo gerenciado externamente, e atualmente é {value}.\\nIsso pode causar problemas caso não seja intencional.\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Seu sistema não possui hyprland-guiutils instalado. Essa é uma dependência de execução para alguns diálogos. Considere instalá-lo.\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"O Hyprland falhou ao carregar {count} recurso essencial, culpe o empacotador da sua distro por fazer um péssimo trabalho!\";\n        return \"O Hyprland falhou ao carregar {count} recursos essenciais, culpe o empacotador da sua distro por fazer um péssimo trabalho!\";\n    });\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"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 \"\n                            \"a wiki (Monitors page) para \"\n                            \"mais informações. Isso <b>vai</b> causar problemas.\");\n    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}.\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Um fator de escala inválido foi passado para o monitor {name}: {scale}, usando o fator sugerido: {fixed_scale}\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Falha ao carregar o plugin {name}: {error}\");\n    huEngine->registerEntry(\"pt_BR\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Falha ao carregar o shader CM, voltando para rgba/rgbx.\");\n    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.\");\n\n    // es (Spanish)\n    huEngine->registerEntry(\"es\", TXT_KEY_ANR_TITLE, \"La aplicación no responde\");\n    huEngine->registerEntry(\"es\", TXT_KEY_ANR_CONTENT, \"La aplicación {title} - {class} no responde.\\n¿Qué deseas hacer?\");\n    huEngine->registerEntry(\"es\", TXT_KEY_ANR_OPTION_TERMINATE, \"Forzar cierre\");\n    huEngine->registerEntry(\"es\", TXT_KEY_ANR_OPTION_WAIT, \"Esperar\");\n    huEngine->registerEntry(\"es\", TXT_KEY_ANR_PROP_UNKNOWN, \"(desconocido)\");\n\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Una aplicación <b>{app}</b> está solicitando un permiso desconocido.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Una aplicación <b>{app}</b> está intentando capturar la pantalla.\\n\\n¿Deseas permitirlo?\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Una aplicación <b>{app}</b> está intentando cargar un plugin: <b>{plugin}</b>.\\n\\n¿Deseas permitirlo?\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Se ha detectado un nuevo teclado: <b>{keyboard}</b>.\\n\\n¿Deseas permitir su uso?\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(desconocido)\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_TITLE, \"Solicitud de permiso\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_PERSISTENCE_HINT,\n                            \"Sugerencia: puedes establecer reglas persistentes para estos permisos en el archivo de configuración de Hyprland.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_ALLOW, \"Permitir\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Permitir y recordar\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Permitir una vez\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_DENY, \"Denegar\");\n    huEngine->registerEntry(\"es\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Aplicación desconocida (ID de cliente de Wayland: {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"es\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"La variable de entorno XDG_CURRENT_DESKTOP parece gestionarse externamente; su valor actual es {value}.\\nEsto podría causar problemas a menos que sea intencional.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Tu sistema no tiene instalado 'hyprland-guiutils'. Es una dependencia en tiempo de ejecución para algunos diálogos. Considera instalarlo.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"No se pudo cargar {count} recurso esencial. Contacta al empaquetador de tu distribución.\";\n        return \"No se pudieron cargar {count} recursos esenciales. Contacta al empaquetador de tu distribución.\";\n    });\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"La configuración de tus monitores no es correcta. El monitor {name} se superpone con otros monitores en la disposición. Consulta la wiki (página \"\n                            \"Monitors, en inglés) para más información. Esto <b>provocará</b> problemas.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"El monitor {name} no pudo configurar ninguno de los modos solicitados y ha vuelto al modo {mode}.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Se pasó una escala no válida al monitor {name}: {scale}; se usará la escala sugerida: {fixed_scale}\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Error al cargar el plugin {name}: {error}\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Error al recargar el shader CM; volviendo a rgba/rgbx.\");\n    huEngine->registerEntry(\"es\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitor {name}: la gama de color amplia está habilitada, pero la pantalla no está en modo de 10 bits.\");\n\n    // fa_IR (Persian)\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_ANR_TITLE, \"برنامه پاسخ نمی‌دهد\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_ANR_CONTENT, \"برنامه {title} - {class} پاسخی نمی‌دهد.\\nمی‌خواهید چه کاری انجام شود؟\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_ANR_OPTION_TERMINATE, \"بستن برنامه\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_ANR_OPTION_WAIT, \"صبر کنید\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_ANR_PROP_UNKNOWN, \"(نامشخص)\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"برنامه <b>{app}</b> در حال درخواست یک مجوز ناشناخته است.\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY,\n                            \"برنامه <b>{app}</b> می‌خواهد صفحه‌نمایش شما را ضبط کند.\\n\\nآیا اجازه می‌دهید؟\");\n\n    huEngine->registerEntry(\n        \"fa_IR\", TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n        \"برنامه <b>{app}</b> می‌خواهد پلاگین <b>{plugin}</b> را بارگذاری کند.\\n\\nآیا اجازه می‌دهید پلاگین بارگذاری \"\n        \"شود؟\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD,\n                            \"یک کیبورد جدید شناسایی شد: <b>{keyboard}</b>.\\n\\nآیا اجازه استفاده از آن را صادر می‌کنید؟\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(نامشخص)\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_TITLE, \"درخواست مجوز\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_PERSISTENCE_HINT,\n                            \"نکته: می‌توانید قوانین دائمی مرتبط را در فایل تنظیمات هایپرلند تعریف کنید.\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_ALLOW, \"اجازه\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"اجازه و ذخیره\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"اجازه یک‌بار\");\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_DENY, \"عدم اجازه\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"برنامه ناشناخته (شناسه Wayland: {wayland_id})\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"متغیر XDG_CURRENT_DESKTOP توسط محیطی خارجی تنظیم شده است و مقدار فعلی آن {value} است.\\n\"\n                            \"اگر این کار عمدی نباشد ممکن است باعث ایجاد مشکل شود.\");\n\n    huEngine->registerEntry(\n        \"fa_IR\", TXT_KEY_NOTIF_NO_GUIUTILS,\n        \"بستهٔ hyprland-guiutils در سیستم نصب نیست. این بسته برای برخی از پنجره‌ها و دیالوگ‌ها لازم است. نصب \"\n        \"آن \"\n        \"پیشنهاد \"\n        \"می‌شود.\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"هایپرلند نتوانست یک فایل ضروری را بارگذاری کند؛ ممکن است بسته‌بندی توزیع مشکل داشته \"\n                   \"باشد.\";\n        return \"هایپرلند نتوانست {count} فایل ضروری را بارگذاری کند؛ ممکن است بسته‌بندی توزیع مشکل داشته \"\n               \"باشد.\";\n    });\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"چیدمان مانیتورها صحیح نیست. مانیتور {name} با یک یا چند مانیتور دیگر تداخل دارد.\\n\"\n                            \"برای اطلاعات بیشتر به صفحهٔ مانیتورها در ویکی مراجعه کنید. این موضوع <b>حتماً</b> باعث مشکل \"\n                            \"می‌شود.\");\n\n    huEngine->registerEntry(\n        \"fa_IR\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL,\n        \"مانیتور {name} نتوانست هیچ‌کدام از حالت‌های درخواستی را اعمال کند؛ بازگشت به حالت {mode}.\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"مقیاس واردشده برای مانیتور {name} نامعتبر است: {scale}. مقیاس پیشنهادی اعمال شد: {fixed_scale}\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"بارگذاری پلاگین {name} با خطا روبه‌رو شد: {error}\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"بارگذاری دوبارهٔ شیدر CM ناموفق بود؛ از حالت rgba/rgbx استفاده شد.\");\n\n    huEngine->registerEntry(\"fa_IR\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"مانیتور {name}: گسترهٔ رنگ وسیع فعال است اما نمایشگر در حالت ۱۰ بیتی نیست.\");\n\n    // fi_FI (Finnish)\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_ANR_TITLE, \"Sovellus ei vastaa\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_ANR_CONTENT, \"Sovellus {title} - {class} ei vastaa.\\nMitä haluat tehdä sille?\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_ANR_OPTION_TERMINATE, \"Lopeta\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_ANR_OPTION_WAIT, \"Odota\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_ANR_PROP_UNKNOWN, \"(tuntematon)\");\n\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Sovellus <b>{app}</b> pyytää tuntematonta käyttöoikeutta.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Sovellus <b>{app}</b> yrittää nauhoittaa näyttöäsi.\\n\\nHaluatko sallia nauhoituksen?\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Sovellus <b>{app}</b> yrittää ladata laajennusta: <b>{plugin}</b>.\\n\\nHaluatko sallia latauksen?\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Uusi näppäimistö havaittu: <b>{keyboard}</b>.\\n\\nHaluatko sallia sen toiminnan?\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(tuntematon)\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_TITLE, \"Käyttöoikeuspyyntö\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Vihje: voit asettaa nämä säännöt pysyvästi Hyprland konfiguraatio tiedostossa.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_ALLOW, \"Salli\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Salli ja muista\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Salli kerran\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_DENY, \"Kiellä\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Tuntematon sovellus (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"XDG_CURRENT_DESKTOP ympäristösi näyttäisi olevan ulkoisesti hallittu, ja sen nykyinen arvo on {value}.\\nTämä voi aiheuttaa ongelmia, jos sitä ei ole \"\n                            \"tehty tarkoituksella.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Paketti hyprland-guiutils ei ole asennettuna järjestelmääsi. Jotkin dialogit tarvitsevat sitä. Harkitse sen asentamista.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland epäonnistui olennaisen resurssin ({count}) latauksessa. Tämä johtuu todennäköisesti jakelusi virheellisestä pakkauksesta.\";\n        return \"Hyprland epäonnistui olennaisten resurssien ({count}) latauksessa. Tämä johtuu todennäköisesti jakelusi virheellisestä pakkauksesta.\";\n    });\n    huEngine->registerEntry(\n        \"fi_FI\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"Näyttöjesi asettelu on virheellinen. Näyttö {name} on muiden näyttöjen päällä.\\nLisätietoja löydät wikistä (Monitors sivu). Tämä <b>tulee aiheuttamaan</b> ongelmia.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Näyttö {name} epäonnistui pyydetyn tilan asettamisessa, palataan tilaan {mode}.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Näytölle {name} asetettu skaalaus: {scale} on virheellinen, asetetaan suositeltu skaalaus: {fixed_scale}.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Laajennuksen {name} lataus epäonnistui: {error}\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM varjostimen uudelleenlataus epäonnistui, palataan takaisin rgba/rgbx tilaan.\");\n    huEngine->registerEntry(\"fi_FI\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Näyttö {name}: laaja väriskaala on otettu käyttöön, mutta näyttö ei ole 10-bit tilassa.\");\n\n    // fr_FR (French)\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_ANR_TITLE, \"L'application ne répond plus\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_ANR_CONTENT, \"L'application {title} - {class} ne répond plus.\\nQue voulez-vous faire?\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_ANR_OPTION_TERMINATE, \"Forcer l'arrêt\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_ANR_OPTION_WAIT, \"Attendre\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_ANR_PROP_UNKNOWN, \"(inconnu)\");\n\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Une application <b>{app}</b> demande une autorisation inconnue.\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Une application <b>{app}</b> tente de capturer votre écran.\\n\\nVoulez-vous l'autoriser?\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Une application <b>{app}</b> tente de charger un module : <b>{plugin}</b>.\\n\\nVoulez-vous l'autoriser?\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Un nouveau clavier a été détecté : <b>{keyboard}</b>.\\n\\nVoulez-vous l'autoriser à fonctionner?\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(inconnu)\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_TITLE, \"Demande d'autorisation\");\n    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.\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_ALLOW, \"Autoriser\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Autoriser et mémoriser\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Autoriser une fois\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_DENY, \"Refuser\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Application inconnue (ID client wayland {wayland_id})\");\n\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"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 \"\n                            \"problèmes si ce n'est pas intentionnel.\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Votre système n'a pas hyprland-guiutils installé. C'est une dépendance d'éxécution pour certains dialogues. Envisagez de l'installer.\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland n'a pas pu charger {count} ressource essentielle, cela indique très probablement un problème dans le paquet de votre distribution.\";\n        return \"Hyprland n'a pas pu charger {count} ressources essentielles, cela indique très probablement un problème dans le paquet de votre distribution.\";\n    });\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Votre disposition d'écrans est incorrecte. Le moniteur {name} chevauche un ou plusieurs autres.\\nVeuillez consulter le wiki (page Moniteurs) pour\"\n                            \"en savoir plus. Cela <b>causera</> des problèmes.\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Le moniteur {name} n'a pu appliquer les modes demandés, retour au mode {mode}.\");\n    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}.\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Échec du chargement du module {name} : {error}\");\n    huEngine->registerEntry(\"fr_FR\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Le rechargement du shader CM a échoué, retour aux formats rgba/rgbx\");\n    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.\");\n\n    // hi_IN (Hindi)\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_ANR_TITLE, \"एप्लिकेशन प्रतिक्रिया नहीं दे रहा है\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_ANR_CONTENT,\n                            \"एक एप्लिकेशन {title} - {class} प्रतिक्रिया नहीं दे रहा \"\n                            \"है।\\nआप इसके साथ क्या करना चाहेंगे?\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_ANR_OPTION_TERMINATE, \"समाप्त करें\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_ANR_OPTION_WAIT, \"इंतजार करें\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_ANR_PROP_UNKNOWN, \"(अज्ञात)\");\n\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"एक एप्लिकेशन <b>{app}</b> एक अज्ञात अनुमति का अनुरोध कर रहा है।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY,\n                            \"एक एप्लिकेशन <b>{app}</b> आपकी स्क्रीन कैप्चर करने की \"\n                            \"कोशिश कर रहा है।\\n\\nक्या आप इसे अनुमति देना चाहते हैं?\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n                            \"एक एप्लिकेशन <b>{app}</b> एक प्लगइन लोड करने की कोशिश कर रहा है: \"\n                            \"<b>{plugin}</b>.\\n\\nक्या आप इसे अनुमति देना चाहते हैं?\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD,\n                            \"नया कीबोर्ड पाया गया: <b>{keyboard}</b>.\\n\\nक्या आप \"\n                            \"इसे काम करने की अनुमति देना चाहते हैं?\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(अज्ञात)\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_TITLE, \"अनुमति अनुरोध\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"संकेत: आप Hyprland कॉन्फ़िग फ़ाइल में इनके लिए स्थायी नियम सेट कर सकते हैं।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_ALLOW, \"अनुमति दें\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"अनुमति दें और याद रखें\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"एक बार अनुमति दें\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_DENY, \"अस्वीकार करें\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"अज्ञात एप्लिकेशन (wayland क्लाइंट ID {wayland_id})\");\n\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"आपका XDG_CURRENT_DESKTOP परिवेश बाहरी रूप से प्रबंधित लगता है, और वर्तमान मान \"\n                            \"{value} है।\\nयह समस्या पैदा कर सकता \"\n                            \"है जब तक कि यह जानबूझकर न किया गया हो।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"आपके सिस्टम में hyprland-guiutils इंस्टॉल नहीं है। यह कुछ संवादों के लिए एक रनटाइम \"\n                            \"निर्भरता है। इसे इंस्टॉल करने पर विचार करें।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland {count} आवश्यक संसाधन लोड करने में विफल रहा, अपने डिस्ट्रो \"\n                   \"के पैकेजर को पैकेजिंग में खराब काम करने का दोष दें!\";\n        return \"Hyprland {count} आवश्यक संसाधनों को लोड करने में विफल रहा, अपने \"\n               \"डिस्ट्रो के पैकेजर को पैकेजिंग में खराब काम करने का दोष दें!\";\n    });\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"आपका मॉनिटर लेआउट गलत तरीके से सेट है। मॉनिटर {name} लेआउट में अन्य मॉनिटर(ओं) के \"\n                            \"साथ ओवरलैप कर रहा है।\\nकृपया विकि \"\n                            \" (Monitors पेज) देखें। यह <b>समस्याएँ</b> पैदा करेगा।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL,\n                            \"मॉनिटर {name} ने किसी भी अनुरोधित मोड को सेट करने में \"\n                            \"विफल रहा, मोड {mode} पर वापस जा रहा है।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"मॉनिटर {name} को अवैध स्केल दिया गया: {scale}, सुझाया \"\n                            \"गया स्केल इस्तेमाल किया जा रहा है: {fixed_scale}\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"प्लगइन {name} लोड करने में विफल: {error}\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM शेडर रीलोड विफल हुआ, rgba/rgbx पर वापस जा रहा है।\");\n    huEngine->registerEntry(\"hi_IN\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"मॉनिटर {name}: वाइड कलर गैम सक्षम है लेकिन डिस्प्ले 10-बिट मोड में नहीं है।\");\n\n    // id_ID (Indonesia)\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_ANR_TITLE, \"Aplikasi Tidak Merespon\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_ANR_CONTENT, \"Aplikasi {title} - {class} tidak merespon.\\nApa yang ingin Anda lakukan?\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_ANR_OPTION_TERMINATE, \"Hentikan\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_ANR_OPTION_WAIT, \"Tunggu\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_ANR_PROP_UNKNOWN, \"(tidak diketahui)\");\n\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Aplikasi <b>{app}</b> meminta izin yang tidak dikenali.\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Aplikasi <b>{app}</b> mencoba merekam layar Anda.\\n\\nApakah Anda mengizinkannya?\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Aplikasi <b>{app}</b> mencoba memuat <i>plugin</i>: <b>{plugin}</b>.\\n\\nApakah Anda mengizinkannya?\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Keyboard baru terdeteksi: <b>{keyboard}</b>.\\n\\nApakah Anda mengizinkannya beroperasi?\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(tidak diketahui)\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_TITLE, \"Permintaan Izin\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Petunjuk: Anda dapat mengatur <i>rule</i> ini secara permanen di <i>file</i> konfigurasi Hyprland.\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_ALLOW, \"Izinkan\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Izinkan dan Ingat\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Izinkan Sekali\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_DENY, \"Tolak\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Aplikasi tidak dikenal (ID klien wayland {wayland_id})\");\n\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Variabel <i>environment</i> XDG_CURRENT_DESKTOP Anda tampaknya dikelola secara eksternal, nilainya saat ini: {value}.\\nHal ini dapat menyebabkan \"\n                            \"masalah, kecuali jika disengaja.\");\n    huEngine->registerEntry(\n        \"id_ID\", TXT_KEY_NOTIF_NO_GUIUTILS,\n        \"hyprland-guiutils belum terpasang di Sistem Anda. Paket tersebut merupakan dependensi <i>runtime</i> untuk beberapa dialog. Mohon untuk menginstalnya.\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland gagal memuat {count} aset penting. Salahkan pengelola paket distro Anda karena pengemasannya buruk!\";\n        return \"Hyprland gagal memuat {count} aset penting. Salahkan pengelola paket distro Anda karena pengemasannya buruk!\";\n    });\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Susunan monitor Anda tidak benar. Monitor {name} tertumpuk dengan monitor lain.\\nSilakan lihat wiki (halaman <i>Monitors</i>) untuk \"\n                            \"detailnya. Hal ini <b>pasti</b> akan menimbulkan masalah.\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitor {name} gagal menerapkan mode yang diminta, kembali ke mode {mode}.\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Skala tidak valid diberikan ke monitor {name}: {scale}, skala yang disarankan: {fixed_scale}\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Gagal memuat plugin {name}: {error}\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Gagal memuat ulang shader CM, kembali ke rgba/rgbx.\");\n    huEngine->registerEntry(\"id_ID\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitor {name}: <i>wide color gamut</i> aktif tetapi layar tidak dalam mode 10-bit.\");\n\n    // hr_HR (Croatian)\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_ANR_TITLE, \"Aplikacija ne reagira\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_ANR_CONTENT, \"Aplikacija {title} - {class} ne reagira.\\nŠto želiš napraviti s njom?\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_ANR_OPTION_TERMINATE, \"Zaustavi\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_ANR_OPTION_WAIT, \"Pričekaj\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_ANR_PROP_UNKNOWN, \"(nepoznato)\");\n\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Aplikacija <b>{app}</b> zahtijeva nepoznatu dozvolu.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Aplikacija <b>{app}</b> pokušava snimati vaš zaslon.\\n\\nŽeliš li dopustiti?\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Aplikacija <b>{app}</b> pokušava učitati dodatak: <b>{plugin}</b>.\\n\\nŽeliš li dopustiti?\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Otkrivena je nova tipkovnica: <b>{keyboard}</b>.\\n\\nŽeliš li omogućiti njen rad?\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(nepoznato)\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_TITLE, \"Zahtjev za dozvolu\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Savjet: za ovo možeš postaviti trajna pravila u Hyprland konfiguracijskoj datoteci.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_ALLOW, \"Dozvoli\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Dozvoli i zapamti\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Dozvoli samo ovaj put\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_DENY, \"Uskrati\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Nepoznata aplikacija (ID wayland klijenta {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"hr_HR\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Izgleda da je tvoja XDG_CURRENT_DESKTOP okolina vanjski upravljana te je trenutna vrijednost {value}.\\nOvo može izazvati problem, osim ako je namjerno.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Na tvojem sustavu nije instaliran hyprland-guiutils. Ovo je ovisnost tijekom pokretanja nekih dijaloga. Preporučeno je da je instaliraš.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo % 10 <= 1 && assetsNo % 100 != 11)\n            return \"Hyprland nije uspio učitati {count} neophodnu komponentu, krivi pakera svoje distribucije za loš posao pakiranja!\";\n        else if (assetsNo % 10 <= 4 && assetsNo % 100 > 14)\n            return \"Hyprland nije uspio učitati {count} neophodne komponente, krivi pakera svoje distribucije za loš posao pakiranja!\";\n        return \"Hyprland nije uspio učitati {count} neophodnih komponenata, krivi pakera svoje distribucije za loš posao pakiranja!\";\n    });\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Raspored tvojih monitora je krivo postavljen. Monitor {name} preklapa se s ostalim monitorom/ima u rasporedu.\\nProvjeri wiki (Monitors stranicu) za \"\n                            \"više informacija. Ovo <b>hoće</b> izazvati probleme.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitor {name} nije uspio odrediti zatražene načine rada, povratak na zadani način rada: {mode}.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Nevažeći razmjer proslijeđen monitoru {name}: {scale}, koristi se predloženi razmjer: {fixed_scale}\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Učitavanje dodatka {name} nije uspjelo: {error}\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Ponovno učitavanje CM shadera nije uspjelo, povratak na zadano: rgba/rgbx.\");\n    huEngine->registerEntry(\"hr_HR\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitor {name}: široki raspon boja je omogućen, ali ekran nije u 10-bitnom načinu rada.\");\n\n    // it_IT (Italian)\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_ANR_TITLE, \"L'applicazione non risponde\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_ANR_CONTENT, \"Un'applicazione {title} - {class} non risponde.\\nCosa vuoi fare?\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_ANR_OPTION_TERMINATE, \"Termina\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_ANR_OPTION_WAIT, \"Attendi\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_ANR_PROP_UNKNOWN, \"(sconosciuto)\");\n\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Un'applicazione <b>{app}</b> richiede un'autorizzazione sconosciuta.\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Un'applicazione <b>{app}</b> sta provando a catturare il tuo schermo.\\n\\nVuoi permetterglielo?\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n                            \"Un'applicazione <b>{app}</b> sta provando a caricare un plugin: <b>{plugin}</b>.\\n\\nVuoi permetterglielo?\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"È stata rilevata una nuova tastiera: <b>{keyboard}</b>.\\n\\nLe vuoi permettere di operare?\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(sconosciuto)\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_TITLE, \"Richiesta di autorizzazione\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Consiglio: Puoi impostare una regola persistente nel tuo file di configurazione di Hyprland.\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_ALLOW, \"Permetti\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Permetti e ricorda\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Permetti una volta\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_DENY, \"Nega\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Applicazione sconosciuta (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"L'ambiente XDG_CURRENT_DESKTOP sembra essere gestito esternamente, il valore attuale è {value}.\\nSe non è voluto, potrebbe causare problemi.\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Sembra che hyprland-guiutils non sia installato. È una dipendenza richiesta per alcuni dialoghi che potresti voler installare.\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_FAILED_ASSETS,\n                            \"Hyprland non ha potuto caricare {count} asset, dai la colpa al packager della tua distribuzione per il suo cattivo lavoro!\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"I tuoi schermi sono configurati incorrettamente. Lo schermo {name} si sovrappone con altri nel layout.\\nConsulta la wiki (voce Schermi) per \"\n                            \"altre informazioni. Questo <b>causerà</b> problemi.\");\n    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}.\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Fattore di scala non valido per lo schermo {name}: {scale}, utilizzando il fattore suggerito: {fixed_scale}\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Impossibile caricare il plugin {name}: {error}\");\n    huEngine->registerEntry(\"it_IT\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Impossibile ricaricare gli shader CM, sarà usato rgba/rgbx.\");\n    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.\");\n\n    // ja_JP (Japanese)\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_ANR_TITLE, \"アプリが応答しません\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_ANR_CONTENT, \"アプリ {title} - {class} が応答しません。\\nどうしますか？\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_ANR_OPTION_TERMINATE, \"強制終了\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_ANR_OPTION_WAIT, \"待機\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_ANR_PROP_UNKNOWN, \"（不明）\");\n\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"アプリ <b>{app}</b> が権限を求めています。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"アプリ <b>{app}</b> が画面をキャプチャしようとしています。\\n\\n許可しますか？\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"アプリ <b>{app}</b> がプラグイン <b>{plugin}</b> をロードしようとしています。\\n\\n許可しますか？\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"新しいキーボード <b>{keyboard}</b> が接続されました。\\n\\n使用を許可しますか？\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"（不明）\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_TITLE, \"権限の要求\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"ヒント：永続的なルールを Hyprland の設定ファイルに記述できます。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_ALLOW, \"許可\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"許可して保存\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"今回だけ許可\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_DENY, \"却下\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"不明なアプリ（wayland クライアント ID {wayland_id}）\");\n\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"環境変数 XDG_CURRENT_DESKTOP は外部から {value} に設定されています。\\n意図的なものでなければ、何らかの問題を起こすかもしれません。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_NO_GUIUTILS, \"hyprland-guiutils がありません。このパッケージをインストールしてください。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_FAILED_ASSETS,\n                            \"{count} 個の必要なアセットをロードできません。ディストリビューションのパッケージ作成者にこの問題を報告してください。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"モニタのレイアウトが正しく設定されていません。モニタ {name} の表示領域が他のモニタと重複しています。\\n詳細は Wiki の Monitor \"\n                            \"の項目を参照してください。これは<b>絶対に</b>問題を起こします。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"モニタ {name} のモード設定に失敗したため、モード {mode} を使用します。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"モニタ {name} のスケール設定が正しくないため、代わりにスケール {fixed_scale} を使用します。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"プラグイン {name} のロードで、エラー {error} が発生しました。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM シェーダのリロードに失敗したため、rgba/rgbx を使用します。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"広色域が有効なモニタ {name} を使用していますが、画面表示の設定は 10 ビットになっていません。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_NOTIF_NO_WATCHDOG, \"start-hyprland なしで Hyprland を実行しています。これは、デバッグ目的以外ではおすすめしません。\");\n\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_SAFE_MODE_TITLE, \"セーフモード\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"前回のセッションがクラッシュしました。Hyprland \"\n                            \"は設定ファイルをロードしない、セーフモードで動作しています。\\n問題を解決するか、もしくは下のボタンで設定ファイルをロードしてください。\"\n                            \"\\nデフォルトのキーバインドは、SUPER+Q が kitty、SUPER+R が簡素なランチャー、SUPER+M が Hyprland の終了です。\"\n                            \"\\nHyprland を再起動することで、ノーマルモードで動作します。\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"設定ファイルをロード\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"クラッシュレポートフォルダを開く\");\n    huEngine->registerEntry(\"ja_JP\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"了解（このウィンドウを閉じる）\");\n\n    // lv_LV (Latvian)\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_ANR_TITLE, \"Lietotne nereaģē\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_ANR_CONTENT, \"Lietotne {title} - {class} nereaģē.\\nKo jūs vēlaties darīt?\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_ANR_OPTION_TERMINATE, \"Beigt procesu\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_ANR_OPTION_WAIT, \"Gaidīt\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_ANR_PROP_UNKNOWN, \"(nezināms)\");\n\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Lietotne <b>{app}</b> pieprasa nezināmu atļauju.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Lietotne <b>{app}</b> mēģina lasīt no jūsu ekrāna.\\n\\nVai vēlaties to atļaut?\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Lietotne <b>{app}</b> mēģina ielādēt spraudni: <b>{plugin}</b>.\\n\\nVai vēlaties to atļaut?\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Ir atrasta jauna tastatūra: <b>{keyboard}</b>.\\n\\nVai vēlaties atļaut tās darbību?\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(nezināms)\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_TITLE, \"Atļaujas pieprasījums\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Padoms: Hyprland konfigurācijas failā varat arī iestatīt atļaujas.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_ALLOW, \"Atļaut\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Atļaut un atcerēties\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Atļaut vienreiz\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_DENY, \"Aizliegt\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Nezināma lietotne (Wayland klienta ID {wayland_id})\");\n\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Jūsu XDG_CURRENT_DESKTOP tiek ārēji pārvaldīts, tās vērtība ir {value}.\\nTas var neapzināti izraisīt problēmas.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_NO_GUIUTILS, \"Jums nav instalēts hyprland-guiutils. Šī pakotne ir nepieciešama dažiem dialogiem. Apsveriet tās instalēšanu.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland nevarēja ielādēt {count} būtisku resursu, vainojiet sava distro iepakotāju par sliktu iepakošanu!\";\n        return \"Hyprland nevarēja ielādēt {count} būtiskus resursus, vainojiet sava distro iepakotāju par sliktu iepakošanu!\";\n    });\n    huEngine->registerEntry(\n        \"lv_LV\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"Jūsu monitora izkārtojums ir nepareizi iestatīts. Monitors {name} pārklājas ar citiem izkārtojumā iestatītajiem monitoriem.\\nLūdzu apskatieties (Monitoru lapā),\"\n        \"lai uzzinātu vairāk. Tas <b>radīs</b> problēmas.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitoram {name} neizdevās iestatīt nevienu no pieprasītajiem režīmiem, izmantojam {mode}.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Monitoram {name} ir nodots nederīgs mērogs: {scale}, izmantojam ieteikto mērogu: {fixed_scale}\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Nevarēja ielādēt spraudni {name}: {error}\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM šeiderus neizdevās pārlādēt, izmantojam rgba/rgbx.\");\n    huEngine->registerEntry(\"lv_LV\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitors {name}: Ir iespējota plaša krāsu gamma, bet displejs nav 10-bitu režīmā.\");\n\n    // hu_HU (Hungarian)\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_ANR_TITLE, \"Az alkalmazás nem válaszol\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_ANR_CONTENT, \"A(z) {title} - {class} alkalmazás nem válaszol.\\nMit szeretne tenni vele?\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_ANR_OPTION_TERMINATE, \"Leállítás\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_ANR_OPTION_WAIT, \"Várakozás\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_ANR_PROP_UNKNOWN, \"(ismeretlen)\");\n\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"A(z) <b>{app}</b> alkalmazás ismeretlen engedélyt kér.\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"A(z) <b>{app}</b> alkalmazás megpróbálja rögzíteni a képernyőjét.\\n\\nEngedélyezi?\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"A(z) <b>{app}</b> alkalmazás megpróbál egy bővítményt betölteni: <b>{plugin}</b>.\\n\\nEngedélyezi?\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Új billentyűzetet észleltünk: <b>{keyboard}</b>.\\n\\nEngedélyezi a használatát?\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(ismeretlen)\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_TITLE, \"Engedélykérés\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Tipp: Állandó szabályokat állíthat be a Hyprland konfigurációs fájlban.\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_ALLOW, \"Engedélyezés\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Mindig engedélyez\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Egyszeri engedélyezés\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_DENY, \"Elutasítás\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Ismeretlen alkalmazás (wayland kliens ID {wayland_id})\");\n\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Ú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.\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"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.\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            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.\";\n        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.\";\n    });\n    huEngine->registerEntry(\n        \"hu_HU\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"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 \"\n        \"(Monitors oldal). Ez <b>problémákat</b> fog okozni.\");\n    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.\");\n    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}\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Nem sikerült betölteni a(z) {name} bővítményt: {error}\");\n    huEngine->registerEntry(\"hu_HU\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"A CM shader újratöltése sikertelen, visszaáll rgba/rgbx-re.\");\n    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.\");\n\n    // ml_IN (Malayalam)\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_ANR_TITLE, \"ആപ്ലിക്കേഷൻ പ്രതികരിക്കുന്നില്ല\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_ANR_CONTENT, \"ആപ്ലിക്കേഷൻ {title} - {class} പ്രതികരിക്കുന്നില്ല.\\nഇതിന് നിങ്ങൾ എന്ത് ചെയ്യാൻ ആഗ്രഹിക്കുന്നു?\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_ANR_OPTION_TERMINATE, \"അവസാനിപ്പിക്കുക\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_ANR_OPTION_WAIT, \"കാത്തിരിക്കുക\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_ANR_PROP_UNKNOWN, \"(അജ്ഞാതം)\");\n\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"ആപ്ലിക്കേഷൻ <b>{app}</b> ഒരു അജ്ഞാത അനുമതി അഭ്യർത്ഥിക്കുന്നു.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"ആപ്ലിക്കേഷൻ <b>{app}</b> നിങ്ങളുടെ സ്ക്രീൻ പകർത്താൻ ശ്രമിക്കുന്നു.\\n\\nനിങ്ങൾ അത് അനുവദിക്കണോ?\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"ആപ്ലിക്കേഷൻ <b>{app}</b> ഒരു പ്ലഗിൻ ലോഡ് ചെയ്യാൻ ശ്രമിക്കുന്നു: <b>{plugin}</b>.\\n\\nഇത് അനുവദിക്കണോ?\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"പുതിയ കീബോർഡ് കണ്ടെത്തി: <b>{keyboard}</b>.\\n\\nഇത് പ്രവർത്തിക്കാൻ അനുവദിക്കണോ?\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(അജ്ഞാതം)\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_TITLE, \"അനുമതി അഭ്യർത്ഥന\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"സൂചന: Hyprland കോൺഫിഗ് ഫയലിൽ സ്ഥിരനിയമങ്ങൾ സജ്ജമാക്കാം.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_ALLOW, \"അനുവദിക്കുക\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"അനുവദിച്ച് ഓർക്കുക\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"ഒന്നുതവണ അനുവദിക്കുക\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_DENY, \"നിരസിക്കുക\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"അജ്ഞാത അപ്ലിക്കേഷൻ (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"നിങ്ങളുടെ XDG_CURRENT_DESKTOP പരിസ്ഥിതി പുറത്ത് നിന്ന് നിയന്ത്രിക്കപ്പെടുന്നു, ഇപ്പോഴത്തെ മൂല്യം \"\n                            \"{value}.\\nഇത് ഉദ്ദേശ്യമായല്ലെങ്കിൽ പ്രശ്നങ്ങൾ ഉണ്ടാകും.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"നിങ്ങളുടെ സിസ്റ്റത്തിൽ hyprland-guiutils ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. ഇത് ചില ഡയലോഗുകൾക്ക് ആവശ്യമായ \"\n                            \"റൺടൈം ആശ്രയമാണ്. ഇൻസ്റ്റാൾ ചെയ്യുക.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland {count} പ്രധാന അസറ്റ് ലോഡുചെയ്യാൻ പരാജയപ്പെട്ടു, നിങ്ങളുടെ \"\n                   \"ഡിസ്‌ട്രോ \"\n                   \"പാക്കേജർ പിശക് ചെയ്തിരിക്കുന്നു!\";\n        return \"Hyprland {count} പ്രധാന അസറ്റുകൾ ലോഡുചെയ്യാൻ പരാജയപ്പെട്ടു, നിങ്ങളുടെ \"\n               \"ഡിസ്‌ട്രോ \"\n               \"പാക്കേജർ പിശക് ചെയ്തിരിക്കുന്നു!\";\n    });\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"മോണിറ്റർ ലേയൗട്ട് തെറ്റാണ്. മോണിറ്റർ {name} മറ്റുള്ളവയുമായ് ഒതുങ്ങുന്നു.\\nകൂടുതൽ വിവരങ്ങൾക്ക് Wiki \"\n                            \"(Monitors page) കാണുക. ഇത് <b>പ്രശ്നങ്ങൾ ഉണ്ടാക്കും</b>.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"മോണിറ്റർ {name} ആവശ്യപ്പെട്ട മോഡുകൾ സജ്ജമാക്കാൻ പരാജയപ്പെട്ടു, ഇപ്പോൾ {mode} ഉപയോഗിക്കുന്നു.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"മോണിറ്റർ {name} ന് അസാധുവായ സ്കെയിൽ: {scale}, നിർദ്ദേശിച്ച സ്കെയിൽ: {fixed_scale}\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"പ്ലഗിൻ {name} ലോഡ് ചെയ്യാൻ പരാജയപ്പെട്ടു: {error}\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM ഷേഡർ റീലോഡ് പരാജയപ്പെട്ടു, rgba/rgbx ലേക്ക് മാറുന്നു.\");\n    huEngine->registerEntry(\"ml_IN\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"മോണിറ്റർ {name}: വൈഡ് കളർ ഗാമട്ട് പ്രവർത്തനക്ഷമമാണെങ്കിലും, മോഡ് 10-bit അല്ല.\");\n\n    // nb_NO (Norwegian Bokmål)\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_ANR_TITLE, \"Applikasjonen svarer ikke\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_ANR_CONTENT, \"En applikasjon {title} - {class} svarer ikke.\\nHva vil du gjøre med den?\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_ANR_OPTION_TERMINATE, \"Avslutt\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_ANR_OPTION_WAIT, \"Vent\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_ANR_PROP_UNKNOWN, \"(ukjent)\");\n\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"En applikasjon <b>{app}</b> ber om en ukjent tillatelse.\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"En applikasjon <b>{app}</b> prøver å fange skjermen din.\\n\\nVil du tillate den?\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"En applikasjon <b>{app}</b> prøver å laste en plugin: <b>{plugin}</b>.\\n\\nVil du tillate den?\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Et nytt tastatur er oppdaget: <b>{keyboard}</b>.\\n\\nVil du tillate at det opererer?\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(ukjent)\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_TITLE, \"Tillatelsesforespørsel\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Hint: du kan angi vedvarende regler for disse i Hyprland konfigurasjonsfilen.\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_ALLOW, \"Tillat\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Tillat og husk\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Tillat en gang\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_DENY, \"Nekte\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Ukjent applikasjon (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"nb_NO\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"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.\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Ditt system har ikke hyprland-guiutils installert. Dette er en kjøretidsavhengighet for noen dialoger. Vurder å installere den.\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland kunne ikke laste {count} essensiell ressurs, skyld på distroens pakkeansvarlig for å ha gjort en dårlig jobb med pakkingen!\";\n        return \"Hyprland kunne ikke laste {count} essensielle ressurser, skyld på distroens pakkeansvarlig for å ha gjort en dårlig jobb med pakkingen!\";\n    });\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Skjermoppsettet ditt er satt opp feil. Skjerm {name} overlapper med skjerm(er) i oppsettet.\\nSjekk wiki (Skjerm oppsett siden) for \"\n                            \"mer. Dette <b>vil</b> skape problemer.\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Skjerm {name} feilet å sette de forespurte modusene, faller tilbake til modus {mode}.\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Ugyldig skala sendt til skjerm {name}: {scale}, bruker foreslått skala: {fixed_scale}\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Feilet å laste plugin {name}: {error}\");\n    huEngine->registerEntry(\"nb_NO\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM shader omlading feilet, faller tilbake til rgba/rgbx.\");\n    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.\");\n\n    // ne_NP (Nepali)\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_ANR_TITLE, \"एपले रिस्पन्ड गरिरहेको छैन\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_ANR_CONTENT, \"{title} - {class} एपले रिस्पन्ड गरिरहेको छैन।\\nयससँग के गर्न चहानुहुन्छ?\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_ANR_OPTION_TERMINATE, \"टर्मिनेट गर्नुहोस्\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_ANR_OPTION_WAIT, \"पर्खनुहोस्\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_ANR_PROP_UNKNOWN, \"(अज्ञात)\");\n\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"<b>{app}</b> एपले अज्ञात सुविधाको अनुमति मागिरहेको छ।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"<b>{app}</b> एपले स्क्रिन क्याप्चर गर्न खोज्दै छ।\\n\\nयसलाई अनुमति दिन चहानुहुन्छ?\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"<b>{app}</b> एपले एउटा प्लगिन लोड गर्न खोज्दै छ: <b>{plugin}</b>।\\n\\nयसलाई अनुमति दिन चहानुहुन्छ?\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"एउटा नयाँ किबोर्ड डिटेक्ट गरिएको छ: <b>{keyboard}</b>।\\n\\nयसलाई चल्ने अनुमति दिन चहानुहुन्छ?\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(अज्ञात)\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_TITLE, \"अनुमतिको माग\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"टिप: यसको लागि पर्सिस्टेन्ट नियमहरु तपाइँले हाइपरल्यान्डको कन्फीग्युरेसन फाइलमा राख्न सक्नुहुन्छ।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_ALLOW, \"अनुमति दिनुहोस्\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"अनुमति दिनुहोस् र सम्झनुहोस्\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"एकपटक अनुमति दिनुहोस्\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_DENY, \"अनुमति नदिनुहोस्\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"अज्ञात एप (wayland क्लाइन्ट आईडी {wayland_id})\");\n\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"तपाईँको XDG_CURRENT_DESKTOP वातावरण बाहिरबाट व्यवस्थापन भइरहेको जस्तो देखिएको छ, अहिले {value} देखाइरहेको छ।\\nजानीजानी नगरीएको भएमा यसले समस्याहरु निम्त्याउन सक्छ।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_NO_GUIUTILS, \"तपाइँको सिस्टममा hyprland-guiutils इन्सटल गरिएको छैन। केहि डायलगहरुका लागि यो रनटाइम डिपेन्डेन्सी हो। कृपया इन्सटल गर्नुहोला।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"हाइपरल्यान्डले एउटा अत्यावश्यक एसेट लोड गर्न सकेन, तपाइँको डिस्ट्रोको प्याकेजरको प्याकेजिङ गतिलो छैन!\";\n        return \"हाइपरल्यान्डले {count} अत्यावश्यक एसेटहरु लोड गर्न सकेन, तपाइँको डिस्ट्रोको प्याकेजरको प्याकेजिङ गतिलो छैन!\";\n    });\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"तपाइँको मनिटरको लेआउट गलत तरिकाले मिलाइएको छ। लेआउटमा {name} मनिटर अर्को मनिटर वा मनिटरहरुसङ्ग ओभरल्याप भएको छ।\\nथप बुझ्नलाई कृपया विकिको मनिटर पेज हेर्नुहोस्।\"\n                            \"यसले <b>निश्चित रुपमा</b> समस्या निम्त्याउने छ।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"{name} मनिटरले चाहेको कुनै पनि मोड सेट गर्न सकेन, {mode} मोडमा फर्कँदै।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"{name} मनिटरलाई अमान्य स्केल पठाइयो: {scale}, सजेस्ट गरिएको स्केल प्रयोग गर्दै: {fixed_scale}\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"{name} प्लगिन लोेड गर्न सकिएन: {error}\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM shader रिलोड गर्न सकिएन, rgba/rgbx मा फर्कँदै।\");\n    huEngine->registerEntry(\"ne_NP\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"{name} मनिटर: wide color gamut अन छ तर डिस्प्ले 10-bit मोड मा छैन।\");\n\n    // nl_NL (Dutch)\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_ANR_TITLE, \"Applicatie Reageert Niet\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_ANR_CONTENT, \"Een applicatie {title} - {class} reageert niet.\\nWat wilt u doen?\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_ANR_OPTION_TERMINATE, \"Beëindigen\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_ANR_OPTION_WAIT, \"Wachten\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_ANR_PROP_UNKNOWN, \"(onbekend)\");\n\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Een applicatie <b>{app}</b> vraagt om een onbekende machtiging.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Een applicatie <b>{app}</b> probeert uw scherm op te nemen.\\n\\nWilt u dit toestaan?\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Een applicatie <b>{app}</b> probeert een plugin te laden: <b>{plugin}</b>.\\n\\nWilt u dit toestaan?\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD,\n                            \"Een nieuw toetsenbord is gedetecteerd: <b>{keyboard}</b>.\\n\\nWilt u toestemming geven dat het wordt gebruikt?\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(onbekend)\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_TITLE, \"Toestemmingsverzoek\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Tip: U kunt hiervoor vaste regels instellen in het Hyprland-configuratiebestand.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_ALLOW, \"Toestaan\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Toestaan en onthouden\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Één keer toestaan\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_DENY, \"Weigeren\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Onbekende applicatie (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"nl_NL\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"De XDG_CURRENT_DESKTOP omgevingsvariabele lijkt extern beheerd te worden en de huidige waarde is {value}.\\nDit kan problemen veroorzaken, tenzij dit opzettelijk is.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Hyprland-guiutils is niet op uw systeem geïnstalleerd. Dit is een runtime-afhankelijkheid voor sommige dialogen. Overweeg het te installeren.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland kon {count} essentieel bestand niet laden, geef de pakketbeheerder van uw distro de schuld voor slecht verpakkingswerk!\";\n        return \"Hyprland kon {count} essentiële bestanden niet laden, geef de pakketbeheerder van uw distro de schuld voor slecht verpakkingswerk!\";\n    });\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Uw monitorindeling is onjuist ingesteld. Monitor {name} overlapt met één of meerdere andere monitoren in de indeling.\\n\"\n                            \"Zie de wiki (Monitors pagina) voor meer informatie. Dit <b>zal</b> problemen veroorzaken.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL,\n                            \"Monitor {name} is er niet in geslaagd om een van de aangevraagde modi toe te passen en gebruikt nu de modus {mode}.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Ongeldige schaal opgegeven voor monitor {name}: {scale}, de voorgestelde schaal {fixed_scale} wordt gebruikt.\");\n    huEngine->registerEntry(\"nl_NL\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Plugin {name} kon niet worden geladen: {error}\");\n    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.\");\n    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.\");\n\n    // pl_PL (Polish)\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_ANR_TITLE, \"Aplikacja Nie Odpowiada\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_ANR_CONTENT, \"Aplikacja {title} - {class} nie odpowiada.\\nCo chcesz z nią zrobić?\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_ANR_OPTION_TERMINATE, \"Zakończ proces\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_ANR_OPTION_WAIT, \"Czekaj\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_ANR_PROP_UNKNOWN, \"(nieznane)\");\n\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Aplikacja <b>{app}</b> prosi o pozwolenie na nieznany typ operacji.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Aplikacja <b>{app}</b> prosi o dostęp do twojego ekranu.\\n\\nCzy chcesz jej na to pozwolić?\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Aplikacja <b>{app}</b> próbuje załadować plugin: <b>{plugin}</b>.\\n\\nCzy chcesz jej na to pozwolić?\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Wykryto nową klawiaturę: <b>{keyboard}</b>.\\n\\nCzy chcesz jej pozwolić operować?\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(nieznane)\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_TITLE, \"Prośba o pozwolenie\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Podpowiedź: możesz ustawić stałe zasady w konfiguracji Hyprland'a.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_ALLOW, \"Zezwól\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Zezwól i zapamiętaj\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Zezwól raz\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_DENY, \"Odmów\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Nieznana aplikacja (ID klienta wayland {wayland_id})\");\n\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Zmienna środowiska XDG_CURRENT_DESKTOP została ustawiona zewnętrznie na {value}.\\nTo może sprawić problemy, chyba, że jest celowe.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_NO_GUIUTILS, \"Twój system nie ma hyprland-guiutils zainstalowanych, co może sprawić problemy. Zainstaluj pakiet.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo == 1)\n            return \"Nie udało się załadować {count} kluczowego zasobu, wiń swojego packager'a za robienie słabej roboty!\";\n\n        return \"Nie udało się załadować {count} kluczowych zasobów, wiń swojego packager'a za robienie słabej roboty!\";\n    });\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Pozycje twoich monitorów nie są ustawione poprawnie. Monitor {name} wchodzi na inne monitory.\\nWejdź na wiki (stronę Monitory) \"\n                            \"po więcej. To <b>będzie</b> sprawiać problemy.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitor {name} nie zaakceptował żadnego wybranego programu. Użyto {mode}.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Nieprawidłowa skala dla monitora {name}: {scale}, użyto proponowanej skali: {fixed_scale}\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Nie udało się załadować plugin'a {name}: {error}\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Nie udało się przeładować shader'a CM, użyto rgba/rgbx.\");\n    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.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_NOTIF_NO_WATCHDOG,\n                            \"Hyprland został uruchomiony bez start-hyprland. Nie jest to zalecane, chyba, że jest to środowisko do debugowania.\");\n\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_SAFE_MODE_TITLE, \"Tryb Bezpieczny\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"Hyprland został uruchomiony w trybie bezpiecznym, co oznacza, że twoja ostatnia sesja uległa awarii.\\nTryb bezpieczny zapobiega ładowaniu twojej \"\n                            \"konfiguracji. Możesz próbować rozwiązać\"\n                            \"problem w tym środowisku, lub załadować swoją konfigurację przyciskiem poniżej.\\nDomyślne skróty klawiszowe są dostępne: SUPER+Q uruchamia kitty, \"\n                            \"SUPER+R otwiera podstawowy launcher, SUPER+M zamyka Hyprland.\\nUruchomienie ponowne Hyprland'a uruchomi go w trybie normalnym.\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"Załaduj konfigurację\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"Otwórz folder z raportami awarii\");\n    huEngine->registerEntry(\"pl_PL\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"Ok, zamknij to okno\");\n\n    // pt_PT (Portuguese Portugal)\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_ANR_TITLE, \"A aplicação não está a responder\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_ANR_CONTENT, \"Uma aplicação {title} - {class} não está a responder.\\nO que pretendes fazer com ela?\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_ANR_OPTION_TERMINATE, \"Terminar\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_ANR_OPTION_WAIT, \"Esperar\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_ANR_PROP_UNKNOWN, \"(desconhecido)\");\n\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Uma aplicação <b>{app}</b> está a pedir uma permissão desconhecida.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Uma aplicação <b>{app}</b> está a tentar fazer uma captura do ecrã.\\n\\nQueres permiti-lo?\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"A aplicação <b>{app}</b> está a tentar carregar o plugin: <b>{plugin}</b>.\\n\\nQueres permiti-lo?\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Um novo teclado foi detectado: <b>{keyboard}</b>.\\n\\nQueres permitir a sua operação?\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(desconhecido)\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_TITLE, \"Pedido de permissão\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Dica: podes definir regras persistentes para estes no ficheiro de configuração do Hyprland.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_ALLOW, \"Permitir\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Permitir sempre\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Permitir esta vez\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_DENY, \"Recusar\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Aplicação desconhecida (ID de cliente wayland {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"pt_PT\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"O teu ambiente XDG_CURRENT_DESKTOP parece estar a ser gerido externamente, e o valor actual é {value}.\\nIsto pode causar problemas a não ser que seja intencional.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"O teu sistema não tem o hyprland-guiutils instalado. Esta dependência de runtime é necessária para algumas caixas de diálogo, deverias instalá-la.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland não conseguiu carregar {count} asset essencial, podes culpar o gestor de dependências da tua distro por fazer um mau trabalho!\";\n        return \"Hyprland não conseguiu carregar {count} assets essenciais, podes culpar o gestor de dependências da tua distro por fazer um mau trabalho!\";\n    });\n    huEngine->registerEntry(\n        \"pt_PT\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"O layout do teu monitor não está configurado correctamente. Monitor {name} está em conflito com outro(s) monitor(es) no layout.\\nProcura na wiki (página Monitores) para \"\n        \"mais informações. Isto <b>vai</b> causar problemas.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitor {name} falhou ao configurar os modos requisitados, revertento para o modo {mode} de volta.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Resolução inválida para o monitor {name}: {scale}, revertendo para a resolução sugerida: {fixed_scale}\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Falha ao carregar o plugin {name}: {error}\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM shader falhou ao recarregar, revertendo para rgba/rgbx.\");\n    huEngine->registerEntry(\"pt_PT\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitor {name}: gama de cores ampla está activada mas o monitor não está em modo 10-bits.\");\n\n    // zh_CN (Simplified Chinese)\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_ANR_TITLE, \"应用程序未响应\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_ANR_CONTENT, \"应用程序 {title} - {class} 未响应。\\n你想要采取什么行动？\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_ANR_OPTION_TERMINATE, \"终止\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_ANR_OPTION_WAIT, \"等待\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_ANR_PROP_UNKNOWN, \"（未知）\");\n\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"应用程序 <b>{app}</b> 正在请求一个未知的权限。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"应用程序 <b>{app}</b> 想要捕获你的屏幕。\\n\\n允许它这么做吗？\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"应用程序 <b>{app}</b> 想要加载插件： <b>{plugin}</b>。\\n\\n允许它这么做吗？\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"检测到新的键盘 <b>{keyboard}</b> 接入了。\\n\\n允许这个键盘操作你的系统吗？\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"（未知）\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_TITLE, \"权限请求\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"提示：你可以在Hyprland配置中为他们创建永久性的规则。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_ALLOW, \"允许\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"总是允许\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"允许一次\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_DENY, \"阻止\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"未知的应用程序 （Wayland客户端ID {wayland_id}）\");\n\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"你的环境变量XDG_CURRENT_DESKTOP似乎被外部管理，且当前的值为{value}。如果你不是有意这么做，这可能会导致问题。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_NO_GUIUTILS, \"你的系统似乎没有安装hyprland-guiutils。这是一个用于部分对话框的运行时依赖。请考虑安装。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_FAILED_ASSETS, \"Hyprland无法加载{count}个重要资产，问问你发行版的打包者在打包个什么玩意！？\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"你的显示器没有被正确设置。显示器 {name} 和其他显示器的布局重叠了。请看wiki中的“显示器”一章获取更多信息。这<b>会</b>导致问题。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"显示器 {name} 无法被设置为任何请求的模式，将使用 {mode} 兜底。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"显示器 {name} 被设置了非法的缩放：{scale}，将使用建议的缩放：{fixed_scale}\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"无法加载插件 {name}：{error}\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"无法重新加载CM着色器，将使用rgba/rgbx兜底。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"显示器 {name}：宽色域被启用了，但是显示器并不在10-bit模式。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_NOTIF_NO_WATCHDOG, \"Hyprland 启动时未使用 start-hyprland。除非你处于调试环境，否则极度不推荐这样做。\");\n\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_SAFE_MODE_TITLE, \"安全模式\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"Hyprland \"\n                            \"已在安全模式下启动，这意味着你上次会话崩溃了。\\n安全模式会阻止加载你的配置。你可以在此环境中进行故障排除，或者使用下方按钮加载你的配置。\\n默认快\"\n                            \"捷键适用：SUPER+Q 打开 Kitty，SUPER+R 打开简易启动器，SUPER+M 退出。\\n重新启动 \"\n                            \"Hyprland 将再次进入正常模式。\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"加载配置\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"打开崩溃报告目录\");\n    huEngine->registerEntry(\"zh_CN\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"好的，关闭窗口\");\n\n    // zh_TW (Traditional Chinese)\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_ANR_TITLE, \"應用程式沒有回應\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_ANR_CONTENT, \"應用程式 {title} - {class} 沒有回應。\\n您想要怎麼做？\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_ANR_OPTION_TERMINATE, \"強制結束\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_ANR_OPTION_WAIT, \"等待\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_ANR_PROP_UNKNOWN, \"（未知）\");\n\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"應用程式 <b>{app}</b> 正在請求未知的權限。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"應用程式 <b>{app}</b> 試圖擷取您的螢幕畫面。\\n\\n您是否允許？\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"應用程式 <b>{app}</b> 試圖載入外掛：<b>{plugin}</b>。\\n\\n您是否允許？\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"偵測到新鍵盤：<b>{keyboard}</b>。\\n\\n您是否允許它進行操作？\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"（未知）\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_TITLE, \"權限請求\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"提示：您可以在 Hyprland 設定檔中為此建立永久規則。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_ALLOW, \"允許\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"總是允許\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"僅允許一次\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_DENY, \"拒絕\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"未知的應用程式 （Wayland 用戶端 ID {wayland_id}）\");\n\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"您的 XDG_CURRENT_DESKTOP 環境變數似乎由外部管理，目前的值為 {value}。\\n除非您有意為之，否則這可能會導致問題。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_NO_GUIUTILS, \"您的系統未安裝 hyprland-guiutils。這是部分對話視窗的執行期依賴元件。建議您安裝它。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland 無法載入 {count} 個必要資源，去怪那個把發行版打包成這副德性的維護者！\";\n        return \"Hyprland 無法載入 {count} 個必要資源，去怪那個把發行版打包成這副德性的維護者！\";\n    });\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"您的螢幕配置設定不正確。螢幕 {name} 與配置中的其他螢幕重疊了。\\n請參閱 Wiki（螢幕頁面）以了解詳情。這<b>絕對會</b>導致問題。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"螢幕 {name} 無法設定為任何請求的模式，將改用模式 {mode}。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"傳遞給螢幕 {name} 的縮放比例無效：{scale}，將使用建議的比例：{fixed_scale}\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"無法載入外掛 {name}：{error}\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM 著色器重新載入失敗，將退回使用 rgba/rgbx。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"螢幕 {name}：已啟用廣色域，但顯示器並非處於 10-bit 模式。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_NOTIF_NO_WATCHDOG, \"Hyprland 啟動時未使用 start-hyprland wrapper。除非您處於除錯環境，否則極度不建議這麼做。\");\n\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_SAFE_MODE_TITLE, \"安全模式\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"Hyprland \"\n                            \"已在安全模式下啟動，這代表您的上個工作階段當機。\\n安全模式會阻止載入您的設定檔。您可以在此環境中進行故障排除，或使用下方按鈕載入您的設定。\\n預設快\"\n                            \"捷鍵適用：SUPER+Q 開啟 Kitty，SUPER+R 開啟簡易啟動器，SUPER+M 退出。\\n重新啟動 \"\n                            \"Hyprland 將再次進入正常模式。\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"載入設定檔\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"開啟當機報告目錄\");\n    huEngine->registerEntry(\"zh_TW\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"好，關閉視窗\");\n\n    // ar (Arabic - Modern Standard)\n    huEngine->registerEntry(\"ar\", TXT_KEY_ANR_TITLE, \"التطبيق لا يستجيب\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_ANR_CONTENT, \"التطبيق {title} - {class} لا يستجيب.\\nما الذي تريد فعله؟\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_ANR_OPTION_TERMINATE, \"إنهاء\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_ANR_OPTION_WAIT, \"الانتظار\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_ANR_PROP_UNKNOWN, \"(غير معروف)\");\n\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"يطلب التطبيق <b>{app}</b> صلاحية غير معروفة.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"يحاول التطبيق <b>{app}</b> التقاط الشاشة.\\n\\nهل تريد السماح له بذلك؟\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"يحاول التطبيق <b>{app}</b> تحميل إضافة: <b>{plugin}</b>.\\n\\nهل تريد السماح له بذلك؟\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"تم اكتشاف لوحة مفاتيح جديدة: <b>{keyboard}</b>.\\n\\nهل تريد السماح لها بالعمل؟\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(غير معروف)\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_TITLE, \"طلب الإذن\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"تلميح: يمكنك تعيين قواعد دائمة لهذه الطلبات في ملف إعدادات Hyprland.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_ALLOW, \"السماح\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"السماح مع تذكّر الاختيار\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"السماح لمرة واحدة\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_DENY, \"الرفض\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"تطبيق غير معروف (معرّف عميل Wayland {wayland_id})\");\n\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"يبدو أنّ متغيّر البيئة XDG_CURRENT_DESKTOP يُدار من خارج النظام، والقيمة الحالية هي {value}.\\n\"\n                            \"قد يؤدي ذلك إلى مشكلات ما لم يكن مقصودًا.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_NO_GUIUTILS, \"لا يحتوي نظامك على الحزمة hyprland-guiutils مثبتة. هذه حزمة مطلوبة أثناء التشغيل لبعض مربعات الحوار. يُنصَح بتثبيتها.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"فشل Hyprland في تحميل مورد أساسي ({count}). قد يكون السبب سوء تغليف الحزم في التوزيعة.\";\n        return \"فشل Hyprland في تحميل {count} من الموارد الأساسية. قد يكون السبب سوء تغليف الحزم في التوزيعة.\";\n    });\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"تم إعداد مخطط الشاشات لديك بشكل غير صحيح. الشاشة {name} تتداخل مع شاشة أو أكثر في المخطط.\\n\"\n                            \"يرجى مراجعة صفحة الشاشات في الويكي لمزيد من التفاصيل. هذا <b>سيسبب</b> مشكلات.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"فشلت الشاشة {name} في ضبط أي من الأوضاع المطلوبة، وسيتم الرجوع إلى الوضع {mode}.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"تم تمرير قيمة تحجيم غير صالحة إلى الشاشة {name}: {scale}. سيتم استخدام قيمة التحجيم المقترحة: {fixed_scale}.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"فشل تحميل الإضافة {name}: {error}\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"فشلت إعادة تحميل نظام إدارة الألوان (CM). سيتم الرجوع إلى صيغة الألوان rgba/rgbx.\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"الشاشة {name}: تم تفعيل نطاق الألوان الواسع، لكن العرض ليس في وضع 10 بت.\");\n\n    huEngine->registerEntry(\"ar\", TXT_KEY_SAFE_MODE_TITLE, \"الوضع الآمن\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"شُغل Hyprland في الوضع الآمن، هذا يعني أن جلستك الأخيرة قد انهارت.\\nالوضع الآمن يمنع تحميل إعداداتك، \"\n                            \"يمكنك البحث عن وحل المشاكل في هذه البيئة، أو تحميل إعداداتك باستخدام الزر أدناه.\\n اختصارات المفاتيح الافتراضية: الطرفية (Kitty) — SUPER+Q، مشغّل \"\n                            \"الأوامر البسيط — SUPER+R، الخروج — SUPER+M.\\n\"\n                            \"إعادة تشغيل Hyprland سيشغله في الوضع العادي\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"حمل ملف الإعدادات\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"افتح مجلد تقرير الانهيار\");\n    huEngine->registerEntry(\"ar\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"حسنًا، أغلق هذا\");\n\n    // ro_RO (Romanian)\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_ANR_TITLE, \"Aplicația Nu Răspunde\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_ANR_CONTENT, \"O aplicație {title} - {class} nu răspunde.\\nCe vrei să faci cu ea?\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_ANR_OPTION_TERMINATE, \"Închide\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_ANR_OPTION_WAIT, \"Așteaptă\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_ANR_PROP_UNKNOWN, \"(necunoscut)\");\n\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"O aplicație <b>{app}</b> solicită o permisiune necunoscută.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"O aplicație <b>{app}</b> încearcă să captureze ecranul.\\n\\nDorești să îi permiți acest lucru?\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n                            \"O aplicație <b>{app}</b> încearcă să încarce un plugin: <b>{plugin}</b>.\\n\\nDorești să îi permiți acest lucru?\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"A fost detectată o tastatură nouă: <b>{keyboard}</b>.\\n\\nDorești să îi permiți să funcționeze?\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(necunoscut)\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_TITLE, \"Cerere de permisiune\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Indiciu: poți seta reguli persistente pentru acestea în fișierul de configurare Hyprland.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_ALLOW, \"Permite\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Permite și reține\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Permite o dată\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_DENY, \"Respinge\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Aplicație necunoscută (ID client wayland {wayland_id})\");\n\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Se pare că mediul tău XDG_CURRENT_DESKTOP este gestionat extern, iar valoarea curentă este {value}.\\nAcest lucru ar putea cauza probleme, cu excepția \"\n                            \"cazului în care este intenționat.\");\n    huEngine->registerEntry(\n        \"ro_RO\", TXT_KEY_NOTIF_NO_GUIUTILS,\n        \"Sistemul tău nu are instalat hyprland-guiutils. Aceasta este o dependență de execuție pentru anumite dialoguri. Ia în considerare instalarea acesteia.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo == 1)\n            return \"Hyprland nu a reușit să încarce un element esențial. Dă vina pe packager-ul distro-ului tău că a făcut o treabă proastă la ambalare!\";\n        return \"Hyprland nu a reușit să încarce {count} elemente esențiale. Dă vina pe packager-ul distro-ului tău că a făcut o treabă proastă la ambalare!\";\n    });\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Configurația monitorului este incorectă. Monitorul {name} se suprapune cu alte monitoare.\\nConsultați wiki-ul (pagina Monitoare) pentru \"\n                            \"mai multe informații. Acest lucru <b>va cauza</b> probleme.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitorul {name} nu a reușit să seteze niciun mod solicitat, revenind la modul {mode}.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Scară nevalidă transmisă monitorului {name}: {scale}, se utilizează scara sugerată: {fixed_scale}\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Nu s-a putut încărca pluginul {name}: {error}\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Reîncărcarea shaderului CM a eșuat, revenind la rgba/rgbx.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitor {name}: gama largă de culori este activată, dar afișajul nu este în modul pe 10 biți.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_NOTIF_NO_WATCHDOG,\n                            \"Hyprland a fost pornit fără start-hyprland. Acest lucru nu este recomandat decât dacă te afli într-un mediu de depanare.\");\n\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_SAFE_MODE_TITLE, \"Modul de Siguranță\");\n    huEngine->registerEntry(\n        \"ro_RO\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n        \"Hyprland a fost lansat în modul de siguranță, ceea ce înseamnă că ultima sesiune s-a blocat.\\nModul de siguranță împiedică încărcarea configurației. Poți \"\n        \"depana în acest mediu sau să încarci configurația cu butonul de mai jos.\\nSe aplică combinațiile de taste implicite: SUPER+Q pentru kitty, SUPER+R pentru un runner de \"\n        \"bază.\"\n        \"SUPER+M pentru ieșire.\\nLa repornire \"\n        \"Hyprland se va lansa din nou în modul normal.\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"Încarcă configurația\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"Deschide locația rapoartelor de crash-uri\");\n    huEngine->registerEntry(\"ro_RO\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"Ok, închide\");\n\n    // ru_RU (Russian)\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_ANR_TITLE, \"Приложение не отвечает\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_ANR_CONTENT, \"Приложение {title} - {class} не отвечает.\\nЧто вы хотите сделать?\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_ANR_OPTION_TERMINATE, \"Завершить\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_ANR_OPTION_WAIT, \"Подождать\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_ANR_PROP_UNKNOWN, \"(неизвестно)\");\n\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Приложение <b>{app}</b> запрашивает неизвестное разрешение.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Приложение <b>{app}</b> пытается получить доступ к вашему экрану.\\n\\nРазрешить?\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Приложение <b>{app}</b> пытается загрузить плагин: <b>{plugin}</b>.\\n\\nРазрешить?\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Обнаружена новая клавиатура: <b>{keyboard}</b>.\\n\\nРазрешить ей работать?\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(неизвестно)\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_TITLE, \"Запрос разрешения\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Подсказка: вы можете настроить постоянные правила для этого в конфигурационном файле Hyprland.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_ALLOW, \"Разрешить\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Разрешить и запомнить\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Разрешить в этот раз\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_DENY, \"Отклонить\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Неизвестное приложение (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"ru_RU\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Переменная окружения XDG_CURRENT_DESKTOP установлена извне, текущее значение: {value}.\\nЭто может вызвать проблемы, если только это не сделано намеренно.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_NO_GUIUTILS, \"Пакет hyprland-guiutils не установлен. Он необходим для некоторых диалогов. Рекомендуется установить его.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Не удалось загрузить {count} критически важный ресурс, пожалуйтесь мейнтейнеру вашего дистрибутива за кривую сборку пакета!\";\n        return \"Не удалось загрузить {count} критически важных ресурсов, пожалуйтесь мейнтейнеру вашего дистрибутива за кривую сборку пакета!\";\n    });\n    huEngine->registerEntry(\n        \"ru_RU\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"Неправильно настроен макет мониторов. Монитор {name} перекрывает другие.\\nПодробнее см. в документации (страница Monitors). Это <b>обязательно</b> вызовет проблемы.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Монитор {name} не смог установить ни один из запрошенных режимов, выбран режим {mode}.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Недопустимый масштаб для монитора {name}: {scale}, используется предложенный масштаб: {fixed_scale}\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Не удалось загрузить плагин {name}: {error}\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Не удалось перезагрузить CM shader, используется rgba/rgbx.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Монитор {name}: расширенный цветовой охват включён, но дисплей не в 10-bit режиме.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_NOTIF_NO_WATCHDOG, \"Hyprland был запущен без start-hyprland. Это крайне не рекомендуется, если только вы не в отладочной среде.\");\n\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_SAFE_MODE_TITLE, \"Безопасный режим\");\n    huEngine->registerEntry(\n        \"ru_RU\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n        \"Hyprland запущен в безопасном режиме, это значит, что ваш прошлый сеанс завершился сбоем.\\nБезопасный режим не загружает ваш конфиг. Вы можете \"\n        \"исправить проблему в этом окружении или загрузить конфиг кнопкой ниже.\\nДействуют стандартные бинды: SUPER+Q запускает kitty, SUPER+R открывает лаунчер, \"\n        \"SUPER+M для выхода.\\nПосле перезапуска Hyprland снова запустится в обычном режиме.\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"Загрузить конфиг\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"Открыть каталог отчётов о сбоях\");\n    huEngine->registerEntry(\"ru_RU\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"Ок, закрыть\");\n\n    // sl_SI (Slovenian)\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_ANR_TITLE, \"Program se ne odziva\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_ANR_CONTENT, \"Program {title} - {class} se ne odziva.\\nKaj želite storiti?\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_ANR_OPTION_TERMINATE, \"Prekini\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_ANR_OPTION_WAIT, \"Počakaj\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_ANR_PROP_UNKNOWN, \"(neznano)\");\n\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Program <b>{app}</b> zahteva neznano dovoljenje.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Program <b>{app}</b> poskuša zajeti vaš zaslon.\\n\\nAli mu želite to dovoliti?\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Program <b>{app}</b> skuša naložiti vtičnik: <b>{plugin}</b>.\\n\\nAli mu želite to dovoliti?\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Nova tipkovnica je bila zaznana: <b>{keyboard}</b>.\\n\\nAli ji želite dovoliti delovanje?\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(neznano)\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_TITLE, \"Zahteva za dovoljenje\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Namig: v Hyprlandovi konfiguracijski datoteki lahko nastavite stalna pravila za dovoljenja.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_ALLOW, \"Dovoli\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Dovoli in si zapomni\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Dovoli enkrat\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Neznan program (ID stranke Wayland: {wayland_id})\");\n\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Zdi se, da je Vaše okolje XDG_CURRENT_DESKTOP upravljano od zunaj, trenutna vrednost je {value}.\\nTo lahko povzroči težave, če ni namerno.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"hyprland-guiutils ni nameščen na vaši napravi. To je odvisnost od izvajalnega okolja za nekatera pogovorna okna. Razmislite o namestitvi.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assertNo = std::stoi(vars.at(\"count\"));\n        if (assertNo <= 1)\n            return \"Hyprlandu ni uspelo naložiti {count} bistvenega sredstva, krivite upravitelja paketov vaše distribucije za slabo opravljeno pakiranje!\";\n        return \"Hyprlandu ni uspelo naložiti {count} bistvenih sredstev, krivite upravitelja paketov vaše distribucije za slabo opravljeno pakiranje!\";\n    });\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Vaša zaslonska razporeditev je napačno nastavljena. Zaslon {monitor} se prekriva z drugimi zasloni v razporeditvi.\\n\"\n                            \"Prosimo poglejte wiki (stran Monitors) za več. To <b>bo</b> povzročilo težave.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Zaslon {name} ni uspel nastaviti nobenega zahtevanega načina, vrnitev k načinu {mode}.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Neveljavna skala je bila posredovana zaslonu {name}: {scale}, uporabljena je predlagana skala: {fixed_scale}\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Vtičnika {name} ni bilo mogoče naložiti: {error}\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Ponovno nalaganje senčnika CM ni uspelo, vrnitev k rgba/rgbx.\");\n    huEngine->registerEntry(\"sl_SI\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Zaslon {name}: širok barvni razpon je omogočen, vendar zaslon ni v 10-bitnem načinu.\");\n\n    // sr_RS (Serbian)\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_ANR_TITLE, \"Апликација не реагује\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_ANR_CONTENT, \"Апликација {title} - {class} не реагује.\\nШта желите да урадите са њом?\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_ANR_OPTION_TERMINATE, \"Прекини\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_ANR_OPTION_WAIT, \"Чекај\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_ANR_PROP_UNKNOWN, \"(непознато)\");\n\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Апликација <b>{app}</b> захтева непознату дозволу.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Апликација <b>{app}</b> покушава да снима твој екран.\\n\\nДа ли желиш да то дозволиш?\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Апликација <b>{app}</b> покушава да учита додатак: <b>{plugin}</b>.\\n\\nДа ли желиш да то дозволиш?\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Нова тастатура је детектована: <b>{keyboard}</b>.\\n\\nДа ли желиш да дозволиш њен рад?\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(непознато)\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_TITLE, \"Захтев за дозволу\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Савет: можеш направити трајна правила за ово у Hyprland конфигурационој датотеци.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_ALLOW, \"Дозволи\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Дозволи и запамти\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Дозволи једном\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_DENY, \"Одбиј\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Непозната апликација (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Изгледа да се твојим XDG_CURRENT_DESKTOP окружењем управља споља, и тренутна вредност је {value}.\\nОво може правити проблеме осим ако је намерно.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Твој систем нема инсталиран hyprland-guiutils. Ово је зависност при покретању за неке дијалоге. Размотри инсталацију.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland није успео да учита {count} кључни ресурс, криви пакера твоје дистрибуције за лоше одрађен посао!\";\n        if (assetsNo <= 4)\n            return \"Hyprland није успео да учита {count} кључна ресурса, криви пакера твоје дистрибуције за лоше одрађен посао!\";\n        return \"Hyprland није успео да учита {count} кључних ресурса, криви пакера твоје дистрибуције за лоше одрађен посао!\";\n    });\n    huEngine->registerEntry(\n        \"sr_RS\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"Твој распоред монитора је неправилно постављен. Монитор {name} се преклапа са другим монитором/мониторима у распореду.\\nМолим те погледај вики (Monitors страницу) за \"\n        \"више информација. Ово <b>ће</b> изазвати проблеме.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Монитор {name} није успео да постави ниједан тражени режим, враћање на режим {mode}.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Невалидна скала прослеђена монитору {name}: {scale}, користи се препоручена скала: {fixed_scale}\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Неуспешно учитавање додатка {name}: {error}\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Поново учитавање CM шејдера није успело, враћање на rgba/rgbx.\");\n    huEngine->registerEntry(\"sr_RS\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Монитор {name}: широк спектар боја је омогућен али екран није у 10-битном режиму.\");\n\n    // sr_RS@latin (Serbian Latin)\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_ANR_TITLE, \"Aplikacija ne reaguje\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_ANR_CONTENT, \"Aplikacija {title} - {class} ne reaguje.\\nŠta želite da uradite sa njom?\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_ANR_OPTION_TERMINATE, \"Prekini\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_ANR_OPTION_WAIT, \"Čekaj\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_ANR_PROP_UNKNOWN, \"(nepoznato)\");\n\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Aplikacija <b>{app}</b> zahteva nepoznatu dozvolu.\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Aplikacija <b>{app}</b> pokušava da snima tvoj ekran.\\n\\nDa li želiš da to dozvoliš?\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Aplikacija <b>{app}</b> pokušava da učita dodatak: <b>{plugin}</b>.\\n\\nDa li želiš da to dozvoliš?\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Nova tastatura je detektovana: <b>{keyboard}</b>.\\n\\nDa li želiš da dozvoliš njen rad?\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(nepoznato)\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_TITLE, \"Zahtev za dozvolu\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Savet: možeš napraviti trajna pravila za ovo u Hyprland konfiguracionoj datoteci.\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_ALLOW, \"Dozvoli\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Dozvoli i zapamti\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Dozvoli jednom\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_DENY, \"Odbij\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Nepoznata aplikacija (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"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.\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Tvoj sistem nema instaliran hyprland-guiutils. Ovo je zavisnost pri pokretanju za neke dijaloge. Razmotri instalaciju.\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprland nije uspeo da učita {count} ključni resurs, krivi pakera tvoje distribucije za loše odrađen posao!\";\n        if (assetsNo <= 4)\n            return \"Hyprland nije uspeo da učita {count} ključna resursa, krivi pakera tvoje distribucije za loše odrađen posao!\";\n        return \"Hyprland nije uspeo da učita {count} ključnih resursa, krivi pakera tvoje distribucije za loše odrađen posao!\";\n    });\n    huEngine->registerEntry(\n        \"sr_RS@latin\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"Tvoj raspored monitora je nepravilno postavljen. Monitor {name} se preklapa sa drugim monitorom/monitorima u rasporedu.\\nMolim te pogledaj wiki (Monitors stranicu) za \"\n        \"više informacija. Ovo <b>će</b> izazvati probleme.\");\n    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}.\");\n    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}\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Neuspešno učitavanje dodatka {name}: {error}\");\n    huEngine->registerEntry(\"sr_RS@latin\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Ponovno učitavanje CM šejdera nije uspelo, vraćanje na rgba/rgbx.\");\n    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.\");\n\n    // tr_TR (Turkish)\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_ANR_TITLE, \"Uygulama Yanıt Vermiyor\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_ANR_CONTENT, \"Bir uygulama {title} - {class} yanıt vermiyor.\\nBununla ne yapmak istiyorsun?\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_ANR_OPTION_TERMINATE, \"Sonlandır\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_ANR_OPTION_WAIT, \"Bekle\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_ANR_PROP_UNKNOWN, \"(bilinmiyor)\");\n\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Bir uygulama <b>{app}</b> bilinmeyen bir izin istiyor.\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Bir uygulama <b>{app}</b> ekran kaydı yapmaya çalışıyor.\\n\\nİzin vermek istiyor musun?\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Bir uygulama <b>{app}</b> bir eklenti kurmaya çalışıyor: <b>{plugin}</b>.\\n\\nİzin vermek istiyor musun?\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Yeni bir klavye algılandı: <b>{keyboard}</b>.\\n\\nÇalışmasına izin vermek istiyor musun?\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(bilinmiyor)\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_TITLE, \"İzin isteği\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"İpucu: Hyprland config dosyasında bunlar için kalıcı kurallar atayabilirsin.\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_ALLOW, \"İzin ver\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"İzin ver ve seçimimi hatırla\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Yalnızca bir defa izin ver\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_DENY, \"Reddet\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Bilinmeyen uygulama (wayland istemci ID {wayland_id})\");\n\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"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.\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"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.\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_NOTIF_FAILED_ASSETS,\n                            \"Hyprland {count} gerekli dosyayı yüklemekte başarısız oldu, kötü bir iş çıkardığı için kullandığın distronun paketleyicisini suçla!\");\n    huEngine->registerEntry(\n        \"tr_TR\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        \"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. \"\n        \"Bu <b>sorunlara yol açacak</b>.\");\n    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.\");\n    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}\");\n    huEngine->registerEntry(\"tr_TR\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"{name} plugini yüklenemedi: {error}\");\n    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.\");\n    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.\");\n\n    // tt_RU (Tatar)\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_ANR_TITLE, \"Программа җавап бирми\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_ANR_CONTENT, \"Программасы {title} - {class} җавап бирми.\\nСез аның белән нәрсә эшләргә телисез?\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_ANR_OPTION_TERMINATE, \"Тәмам итү\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_ANR_OPTION_WAIT, \"Көтү\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_ANR_PROP_UNKNOWN, \"(билгесез)\");\n\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"<b>{app}</b> программасы билгесез рөхсәт сорый.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"<b>{app}</b> программасы сезнең экранны яздырырга тели.\\n\\nРөхсәт бирәсезме?\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"<b>{app}</b> программасы плагин йөкләргә тели: <b>{plugin}</b>.\\n\\nРөхсәт бирәсезме?\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Яңа клавиатура табылды: <b>{keyboard}</b>.\\n\\nАның эшләргә рөхсәт бирәсезме?\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(билгесез)\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_TITLE, \"Рөхсәт сорау\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Киңәш: сез Hyprland көйләү файлында даими кагыйдәләр куя аласыз.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_ALLOW, \"Рөхсәт бирү\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Рөхсәт бирү һәм истә калдыру\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Бер тапкыр рөхсәт бирү\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_DENY, \"Кире кагу\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Билгесез программа (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n                            \"Сезнең XDG_CURRENT_DESKTOP мохите тыштан идарә ителә, хәзерге кыйммәте: {value}.\\n\"\n                            \"Бу теләгән булмаса, проблемалар китереп чыгарырга мөмкин.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"Сезнең системада hyprland-guiutils урнаштырылмаган. Бу кайбер диалоглар өчен кирәкле вакыт бәйлелеге. Урнаштыруны карап чыгыгыз.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_FAILED_ASSETS, \"Hyprland {count} мөһим ресурсны йөкли алмады. Ул дистрибутивыгыз пакетлаучысының хатасы!\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Сезнең мониторлар урнашуы дөрес түгел. {name} мониторы башка монитор белән өстәлә.\\n\"\n                            \"Зинһар, өстәмә мәгълүмат өчен викидагы (Monitors бит) мөрәҗәгать итегез. Бу <b>һичшиксез</b> проблемалар тудырачак.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"{name} мониторы соралган режимнарны куя алмады, {mode} режимына кайта.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"{name} мониторы өчен яраксыз масштаб билгеләнгән: {scale}. Тәкъдим ителгән масштаб кулланыла: {fixed_scale}\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"{name} плагинны йөкләүдә хата: {error}\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"CM шейдерын яңадан йөкләү уңышсыз булды, rgba/rgbx режимына кайтыла.\");\n    huEngine->registerEntry(\"tt_RU\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Монитор {name}: киң төсләр диапазоны кушылган, ләкин дисплей 10-бит режимында түгел.\");\n\n    // uk_UA (Ukrainian)\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_ANR_TITLE, \"Програма не відповідає\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_ANR_CONTENT, \"Програма {title} - {class} не відповідає.\\nЩо ви хочете з нею зробити?\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_ANR_OPTION_TERMINATE, \"Завершити\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_ANR_OPTION_WAIT, \"Чекати\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_ANR_PROP_UNKNOWN, \"(невідомо)\");\n\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Програма <b>{app}</b> запитує невідомий дозвіл.\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Програма <b>{app}</b> намагається захопити екран.\\n\\nДозволити?\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Програма <b>{app}</b> намагається завантажити плагін: <b>{plugin}</b>.\\n\\nДозволити?\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Виявлено нову клавіатуру: <b>{keyboard}</b>.\\n\\nДозволити її використання?\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(невідомо)\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_TITLE, \"Запит дозволу\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Підказка: ви можете встановити постійні правила для дозволів у файлі конфігурації Hyprland.\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_ALLOW, \"Дозволити\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Дозволити та запам'ятати\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Дозволити один раз\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_DENY, \"Заборонити\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Невідома програма (ідентифікатор клієнта wayland {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"uk_UA\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Ваше середовище XDG_CURRENT_DESKTOP, схоже, керується ззовні, і поточне значення становить {value}.\\nЦе може спричинити проблеми, якщо це не зроблено навмисно.\");\n    huEngine->registerEntry(\n        \"uk_UA\", TXT_KEY_NOTIF_NO_GUIUTILS,\n        \"У вашій системі не встановлено hyprland-guiutils. Це залежність, потрібна для роботи деяких діалогових вікон. Розгляньте можливість його встановлення.\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Не вдалося завантажити {count} необхідний ресурс, звинувачуйте пакувальника свого дистрибутива у недобросовісній роботі!\";\n        return \"Не вдалося завантажити {count} необхідних ресурсів, звинувачуйте пакувальника свого дистрибутива у недобросовісній роботі!\";\n    });\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Макет моніторів налаштовано неправильно. Монітор {name} перекриває інші монітори у макеті.\\nБудь ласка, перегляньте wiki (сторінка Monitors). \"\n                            \"Це <b>обов'язково</b> створить проблеми.\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Монітор {name} не зміг встановити жодного із запитуваних режимів, повернення до режиму {mode}.\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n                            \"Неправильний масштаб переданий монітору {name}: {scale}, використання запропонованого масштабу: {fixed_scale}\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Помилка завантаження плагіна {name}: {error}\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Не вдалося перезавантажити шейдер CM, повернення до rgba/rgbx.\");\n    huEngine->registerEntry(\"uk_UA\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Монітор {name}: широка кольорова гама увімкнена, але дисплей не працює в 10-бітному режимі.\");\n\n    // vi_VN (Vietnamese)\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_ANR_TITLE, \"Ứng dụng không phản hồi\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_ANR_CONTENT, \"Ứng dụng {title} - {class} đang bị treo.\\nBạn muốn xử lý thế nào?\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_ANR_OPTION_TERMINATE, \"Buộc dừng\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_ANR_OPTION_WAIT, \"Chờ\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_ANR_PROP_UNKNOWN, \"(không xác định)\");\n\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Ứng dụng <b>{app}</b> đang yêu cầu một quyền không xác định.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Ứng dụng <b>{app}</b> đang cố gắng ghi hình màn hình của bạn.\\n\\nBạn muốn cho phép không?\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, \"Ứng dụng <b>{app}</b> đang cố gắng đọc vị trí chuột của bạn.\\n\\nBạn muốn cho phép không?\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Ứng dụng <b>{app}</b> đang cố gắng tải plugin: <b>{plugin}</b>.\\n\\nBạn muốn cho phép không?\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Phát hiện bàn phím mới: <b>{keyboard}</b>.\\n\\nBạn muốn cho phép bàn phím này hoạt động không?\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(không xác định)\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_TITLE, \"Yêu cầu cấp quyền\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Gợi ý: bạn có thể thiết lập các quyền này trong tệp cấu hình Hyprland.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_ALLOW, \"Cho phép\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Cho phép và ghi nhớ\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Chỉ một lần\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_DENY, \"Từ chối\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Ứng dụng không xác định (wayland client ID {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"vi_VN\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Biến môi trường XDG_CURRENT_DESKTOP dường như đang được thiết lập từ bên ngoài với giá trị là {value}.\\nViệc này có thể gây ra lỗi trừ khi đó là chủ ý của bạn.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_NO_GUIUTILS, \"Hệ thống chưa cài hyprland-guiutils. Một số hộp thoại sẽ không hiển thị nếu thiếu nó.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_FAILED_ASSETS,\n                            \"Hyprland không thể tải {count} tài nguyên quan trọng. Vui lòng báo lỗi cho người đóng gói (packager) của bản phân phối (distro) mà bạn dùng!\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Bố cục màn hình không hợp lệ. Màn hình {name} đang bị đè lên các màn hình khác.\\nVui lòng xem trang Monitors trên wiki để \"\n                            \"khắc phục, nếu không <b>chắc chắn</b> sẽ có lỗi xảy ra.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Màn hình {name} không thể áp dụng chế độ nào được yêu cầu, đang dùng tạm chế độ {mode}.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Tỉ lệ {scale} cho màn hình {name} không hợp lệ, chuyển sang tỷ lệ gợi ý: {fixed_scale}\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Lỗi tải plugin {name}: {error}\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Tải lại CM shader thất bại, đang dùng tạm rgba/rgbx.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Màn hình {name}: dải màu rộng (wide color gamut) khả dụng nhưng màn hình không ở chế độ 10-bit.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_NOTIF_NO_WATCHDOG,\n                            \"Hyprland đã được khởi động mà không thông qua start-hyprland. Việc này không được khuyến khích trừ khi dùng cho mục đích gỡ lỗi.\");\n\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_SAFE_MODE_TITLE, \"Chế độ An toàn (Safe Mode)\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_SAFE_MODE_DESCRIPTION,\n                            \"Phiên hoạt động trước đó đã bị sập (crash).\\nHyprland hiện đang chạy ở chế độ an toàn và không tải tệp cấu hình của bạn. Bạn có thể \"\n                            \"khắc phục sự cố trong môi trường này, hoặc bấm nút bên dưới để thử tải lại cấu hình.\\nCác phím tắt mặc định: SUPER+Q (kitty), SUPER+R (runner), \"\n                            \"SUPER+M (exit).\\nHyprland sẽ về chế độ bình thường sau khi khởi động lại.\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, \"Tải cấu hình\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, \"Mở thư mục báo cáo lỗi (crash report)\");\n    huEngine->registerEntry(\"vi_VN\", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, \"OK, đã hiểu\");\n\n    // cs_CZ (Czech)\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_ANR_TITLE, \"Aplikace Neodpovídá\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_ANR_CONTENT, \"Aplikace {title} - {class} neodpovídá.\\nCo s ní chcete udělat?\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_ANR_OPTION_TERMINATE, \"Ukončit\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_ANR_OPTION_WAIT, \"Počkat\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_ANR_PROP_UNKNOWN, \"(neznámé)\");\n\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, \"Aplikace <b>{app}</b> vyžaduje neznámé oprávnění.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, \"Aplikace <b>{app}</b> se pokouší zaznamenávat vaši obrazovku.\\n\\nChcete jí to povolit?\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_REQUEST_PLUGIN, \"Aplikace <b>{app}</b> se pokouší načíst plugin: <b>{plugin}</b>.\\n\\nChcete jí to povolit?\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, \"Byla detekována nová klávesnice: <b>{keyboard}</b>.\\n\\nChcete jí povolit?\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_UNKNOWN_NAME, \"(neznámé)\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_TITLE, \"Žádost o oprávnění\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_PERSISTENCE_HINT, \"Tip: pro tyto případy můžete nastavit trvalá pravidla v konfiguračním souboru Hyprland.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_ALLOW, \"Povolit\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, \"Povolit a zapamatovat\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_ALLOW_ONCE, \"Povolit jednou\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_DENY, \"Zamítnout\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, \"Neznámá aplikace (ID klienta Wayland {wayland_id})\");\n\n    huEngine->registerEntry(\n        \"cs_CZ\", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        \"Proměnná prostředí XDG_CURRENT_DESKTOP se zdá být spravována externě a její aktuální hodnota je {value}.\\nPokud to není záměr, může to způsobit problémy.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_NO_GUIUTILS,\n                            \"V systému není nainstalován balíček hyprland-guiutils. Jedná se o závislost pro běh některých dialogových oken. Zvažte jeho instalaci.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {\n        int assetsNo = std::stoi(vars.at(\"count\"));\n        if (assetsNo <= 1)\n            return \"Hyprlandu se nepodařilo načíst {count} nezbytnou součást. Za špatně odvedenou práci viňte tvůrce balíčku vaší distribuce!\";\n        if (assetsNo <= 4)\n            return \"Hyprlandu se nepodařilo načíst {count} nezbytné součásti. Za špatně odvedenou práci viňte tvůrce balíčku vaší distribuce!\";\n        return \"Hyprlandu se nepodařilo načíst {count} nezbytných součástí. Za špatně odvedenou práci viňte tvůrce balíčku vaší distribuce!\";\n    });\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n                            \"Rozložení vašich monitorů je nastaveno nesprávně. Monitor {name} se překrývá s ostatními monitory.\\nVíce informací naleznete na wiki \"\n                            \"(stránka Monitors). Toto <b>způsobí</b> problémy.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, \"Monitoru {name} se nepodařilo nastavit žádný z požadovaných režimů, vrací se k režimu {mode}.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, \"Monitoru {name} bylo předáno neplatné měřítko: {scale}, použije se navrhované měřítko: {fixed_scale}\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, \"Nepodařilo se načíst plugin {name}: {error}\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_CM_RELOAD_FAILED, \"Nepodařilo se znovu načíst CM shader, vrací se k rgba/rgbx.\");\n    huEngine->registerEntry(\"cs_CZ\", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, \"Monitor {name}: široký barevný gamut je povolen, ale displej není v 10bitovém režimu.\");\n}\n\nstd::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) {\n    static auto CONFIG_LOCALE = CConfigValue<std::string>(\"general:locale\");\n    std::string locale        = *CONFIG_LOCALE != \"\" ? *CONFIG_LOCALE : localeStr;\n    return huEngine->localizeEntry(locale, key, vars);\n}\n"
  },
  {
    "path": "src/i18n/Engine.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n#include <unordered_map>\n#include <cstdint>\n#include <string>\n\nnamespace I18n {\n\n    enum eI18nKeys : uint8_t {\n        TXT_KEY_ANR_TITLE = 0,\n        TXT_KEY_ANR_CONTENT,\n        TXT_KEY_ANR_OPTION_TERMINATE,\n        TXT_KEY_ANR_OPTION_WAIT,\n        TXT_KEY_ANR_PROP_UNKNOWN,\n\n        TXT_KEY_PERMISSION_REQUEST_UNKNOWN,\n        TXT_KEY_PERMISSION_REQUEST_SCREENCOPY,\n        TXT_KEY_PERMISSION_REQUEST_CURSOR_POS,\n        TXT_KEY_PERMISSION_REQUEST_PLUGIN,\n        TXT_KEY_PERMISSION_REQUEST_KEYBOARD,\n        TXT_KEY_PERMISSION_UNKNOWN_NAME,\n        TXT_KEY_PERMISSION_TITLE,\n        TXT_KEY_PERMISSION_PERSISTENCE_HINT,\n        TXT_KEY_PERMISSION_ALLOW,\n        TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER,\n        TXT_KEY_PERMISSION_ALLOW_ONCE,\n        TXT_KEY_PERMISSION_DENY,\n        TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP,\n\n        TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,\n        TXT_KEY_NOTIF_NO_GUIUTILS,\n        TXT_KEY_NOTIF_FAILED_ASSETS,\n        TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,\n        TXT_KEY_NOTIF_MONITOR_MODE_FAIL,\n        TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,\n        TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN,\n        TXT_KEY_NOTIF_CM_RELOAD_FAILED,\n        TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B,\n        TXT_KEY_NOTIF_NO_WATCHDOG,\n\n        TXT_KEY_SAFE_MODE_TITLE,\n        TXT_KEY_SAFE_MODE_DESCRIPTION,\n        TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR,\n        TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG,\n        TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD,\n    };\n\n    class CI18nEngine {\n      public:\n        CI18nEngine();\n        ~CI18nEngine() = default;\n\n        std::string localize(eI18nKeys key, const std::unordered_map<std::string, std::string>& vars = {});\n    };\n\n    SP<CI18nEngine> i18nEngine();\n};\n"
  },
  {
    "path": "src/includes.hpp",
    "content": "#pragma once\n\n// because C/C++ VS Code intellisense is stupid with includes, we will suppress them here.\n// This suppresses all \"include file not found\" errors.\n#ifdef __INTELLISENSE__\n#pragma diag_suppress 1696\n#endif\n\n#include <getopt.h>\n#include <libinput.h>\n#include <linux/input-event-codes.h>\n#include <csignal>\n#include <cstdio>\n#include <cstdlib>\n#include <sys/wait.h>\n#include <ctime>\n#include <unistd.h>\n#include <wayland-server-core.h>\n\n#define GLES32\n#include <GLES3/gl32.h>\n#include <GLES3/gl3ext.h>\n\n#ifdef NO_XWAYLAND\n#define XWAYLAND false\n#else\n#define XWAYLAND true\n#endif\n\n#include \"SharedDefs.hpp\"\n"
  },
  {
    "path": "src/init/initHelpers.cpp",
    "content": "#include \"initHelpers.hpp\"\n\nbool NInit::isSudo() {\n    return getuid() != geteuid() || !geteuid();\n}\n\nvoid NInit::gainRealTime() {\n    const int          minPrio = sched_get_priority_min(SCHED_RR);\n    int                old_policy;\n    struct sched_param param;\n\n    if (pthread_getschedparam(pthread_self(), &old_policy, &param)) {\n        Log::logger->log(Log::WARN, \"Failed to get old pthread scheduling priority\");\n        return;\n    }\n\n    param.sched_priority = minPrio;\n\n    if (pthread_setschedparam(pthread_self(), SCHED_RR, &param)) {\n        Log::logger->log(Log::WARN, \"Failed to change process scheduling strategy\");\n        return;\n    }\n\n    pthread_atfork(nullptr, nullptr, []() {\n        const struct sched_param param = {.sched_priority = 0};\n        if (pthread_setschedparam(pthread_self(), SCHED_OTHER, &param))\n            Log::logger->log(Log::WARN, \"Failed to reset process scheduling strategy\");\n    });\n}"
  },
  {
    "path": "src/init/initHelpers.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n\nnamespace NInit {\n    bool isSudo();\n    void gainRealTime();\n};"
  },
  {
    "path": "src/layout/LayoutManager.cpp",
    "content": "#include \"LayoutManager.hpp\"\n\n#include \"space/Space.hpp\"\n#include \"target/Target.hpp\"\n\n#include \"../config/ConfigManager.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../desktop/view/Group.hpp\"\n#include \"../event/EventBus.hpp\"\n\nusing namespace Layout;\n\nCLayoutManager::CLayoutManager() = default;\n\nvoid CLayoutManager::newTarget(SP<ITarget> target, SP<CSpace> space) {\n    // on a new target: remember desired pos for float, if available\n    if (const auto DESIRED_GEOM = target->desiredGeometry(); DESIRED_GEOM)\n        target->rememberFloatingSize(DESIRED_GEOM->size);\n\n    target->assignToSpace(space);\n}\n\nvoid CLayoutManager::removeTarget(SP<ITarget> target) {\n    target->assignToSpace(nullptr);\n}\n\nvoid CLayoutManager::changeFloatingMode(SP<ITarget> target) {\n    if (!target->space())\n        return;\n\n    target->space()->toggleTargetFloating(target);\n}\n\nvoid CLayoutManager::beginDragTarget(SP<ITarget> target, eMouseBindMode mode) {\n    m_dragStateController->dragBegin(target, mode);\n}\n\nvoid CLayoutManager::moveMouse(const Vector2D& mousePos) {\n    m_dragStateController->mouseMove(mousePos);\n}\n\nvoid CLayoutManager::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    if (target->isPseudo()) {\n        auto fixedΔ = Δ;\n        if (corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT)\n            fixedΔ.x = -fixedΔ.x;\n        if (corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT)\n            fixedΔ.y = -fixedΔ.y;\n\n        auto       newPseudoSize    = target->pseudoSize() + fixedΔ;\n        const auto TARGET_TILE_SIZE = target->position().size();\n        newPseudoSize.x             = std::clamp(newPseudoSize.x, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.x);\n        newPseudoSize.y             = std::clamp(newPseudoSize.y, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.y);\n\n        target->setPseudoSize(newPseudoSize);\n\n        return;\n    }\n\n    target->space()->resizeTarget(Δ, target, corner);\n}\n\nvoid CLayoutManager::setTargetGeom(const CBox& box, SP<ITarget> target) {\n    if (!target->floating())\n        return;\n\n    target->space()->setTargetGeom(box, target);\n}\n\nstd::expected<void, std::string> CLayoutManager::layoutMsg(const std::string_view& sv) {\n\n    const auto MONITOR = Desktop::focusState()->monitor();\n    // forward to the active workspace\n    if (!MONITOR)\n        return std::unexpected(\"No monitor, can't find ws to target\");\n\n    auto ws = MONITOR->m_activeSpecialWorkspace ? MONITOR->m_activeSpecialWorkspace : MONITOR->m_activeWorkspace;\n\n    if (!ws)\n        return std::unexpected(\"No workspace, can't target\");\n\n    return ws->m_space->layoutMsg(sv);\n}\n\nvoid CLayoutManager::moveTarget(const Vector2D& Δ, SP<ITarget> target) {\n    if (!target->floating())\n        return;\n\n    target->space()->moveTarget(Δ, target);\n}\n\nvoid CLayoutManager::endDragTarget() {\n    m_dragStateController->dragEnd();\n}\n\nvoid CLayoutManager::fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) {\n    if (target->space())\n        target->space()->setFullscreen(target, effectiveMode);\n}\n\nvoid CLayoutManager::switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus) {\n\n    if (preserveFocus) {\n        a->swap(b);\n        return;\n    }\n\n    const auto IS_A_ACTIVE = Desktop::focusState()->window() == a->window();\n    const auto IS_B_ACTIVE = Desktop::focusState()->window() == b->window();\n\n    a->swap(b);\n\n    if (IS_A_ACTIVE && b->window())\n        Desktop::focusState()->fullWindowFocus(b->window(), Desktop::FOCUS_REASON_KEYBIND);\n\n    if (IS_B_ACTIVE && a->window())\n        Desktop::focusState()->fullWindowFocus(a->window(), Desktop::FOCUS_REASON_KEYBIND);\n}\n\nvoid CLayoutManager::moveInDirection(SP<ITarget> target, const std::string& direction, bool silent) {\n    Math::eDirection dir = Math::fromChar(direction.at(0));\n    if (dir == Math::DIRECTION_DEFAULT) {\n        Log::logger->log(Log::ERR, \"invalid direction for moveInDirection: {}\", direction);\n        return;\n    }\n\n    if (!target->space())\n        return;\n\n    target->space()->moveTargetInDirection(target, dir, silent);\n}\n\nSP<ITarget> CLayoutManager::getNextCandidate(SP<CSpace> space, SP<ITarget> from) {\n    return space->getNextCandidate(from);\n}\n\nbool CLayoutManager::isReachable(SP<ITarget> target) {\n    return true;\n}\n\nvoid CLayoutManager::bringTargetToTop(SP<ITarget> target) {\n    if (!target)\n        return;\n\n    if (target->window()->m_group) {\n        // grouped, change the current to this window\n        target->window()->m_group->setCurrent(target->window());\n    }\n}\n\nstd::optional<Vector2D> CLayoutManager::predictSizeForNewTiledTarget() {\n    const auto FOCUSED_MON = Desktop::focusState()->monitor();\n\n    if (!FOCUSED_MON || !FOCUSED_MON->m_activeWorkspace)\n        return std::nullopt;\n\n    if (FOCUSED_MON->m_activeSpecialWorkspace)\n        return FOCUSED_MON->m_activeSpecialWorkspace->m_space->predictSizeForNewTiledTarget();\n\n    return FOCUSED_MON->m_activeWorkspace->m_space->predictSizeForNewTiledTarget();\n}\n\nconst UP<Supplementary::CDragStateController>& CLayoutManager::dragController() {\n    return m_dragStateController;\n}\n\nstatic inline bool canSnap(const double SIDEA, const double SIDEB, const double GAP) {\n    return std::abs(SIDEA - SIDEB) < GAP;\n}\n\nstatic void snapMove(double& start, double& end, const double P) {\n    end   = P + (end - start);\n    start = P;\n}\n\nstatic void snapResize(double& start, double& end, const double P) {\n    start = P;\n}\n\nusing SnapFn = std::function<void(double&, double&, const double)>;\n\nvoid CLayoutManager::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> DRAGGINGTARGET, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) {\n\n    const auto DRAGGINGWINDOW = DRAGGINGTARGET->window();\n\n    if (!Desktop::View::validMapped(DRAGGINGWINDOW))\n        return;\n\n    static auto  SNAPWINDOWGAP     = CConfigValue<Hyprlang::INT>(\"general:snap:window_gap\");\n    static auto  SNAPMONITORGAP    = CConfigValue<Hyprlang::INT>(\"general:snap:monitor_gap\");\n    static auto  SNAPBORDEROVERLAP = CConfigValue<Hyprlang::INT>(\"general:snap:border_overlap\");\n    static auto  SNAPRESPECTGAPS   = CConfigValue<Hyprlang::INT>(\"general:snap:respect_gaps\");\n\n    static auto  PGAPSIN  = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:gaps_in\");\n    static auto  PGAPSOUT = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:gaps_out\");\n    const auto   GAPSNONE = CCssGapData{0, 0, 0, 0};\n\n    const SnapFn SNAP  = (MODE == MBIND_MOVE) ? snapMove : snapResize;\n    int          snaps = 0;\n\n    struct SRange {\n        double start = 0;\n        double end   = 0;\n    };\n    const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);\n    SRange     sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x};\n    SRange     sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y};\n\n    if (*SNAPWINDOWGAP) {\n        const double GAPSIZE       = *SNAPWINDOWGAP;\n        const auto   WSID          = DRAGGINGWINDOW->workspaceID();\n        const bool   HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow;\n\n        const auto*  GAPSIN = *SNAPRESPECTGAPS ? sc<CCssGapData*>(PGAPSIN.ptr()->getData()) : &GAPSNONE;\n        const double GAPSX  = GAPSIN->m_left + GAPSIN->m_right;\n        const double GAPSY  = GAPSIN->m_top + GAPSIN->m_bottom;\n\n        for (auto& other : g_pCompositor->m_windows) {\n            if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut ||\n                other->isX11OverrideRedirect())\n                continue;\n\n            const CBox   SURF   = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS);\n            const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX};\n            const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY};\n\n            // only snap windows if their ranges overlap in the opposite axis\n            if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) {\n                if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) {\n                    SNAP(sourceX.start, sourceX.end, SURFBX.end);\n                    snaps |= SNAP_LEFT;\n                } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) {\n                    SNAP(sourceX.end, sourceX.start, SURFBX.start);\n                    snaps |= SNAP_RIGHT;\n                }\n            }\n            if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) {\n                if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) {\n                    SNAP(sourceY.start, sourceY.end, SURFBY.end);\n                    snaps |= SNAP_UP;\n                } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) {\n                    SNAP(sourceY.end, sourceY.start, SURFBY.start);\n                    snaps |= SNAP_DOWN;\n                }\n            }\n\n            // corner snapping\n            if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) {\n                const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY};\n                if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) {\n                    SNAP(sourceY.start, sourceY.end, SURFY.start);\n                    snaps |= SNAP_UP;\n                } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) {\n                    SNAP(sourceY.end, sourceY.start, SURFY.end);\n                    snaps |= SNAP_DOWN;\n                }\n            }\n            if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) {\n                const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX};\n                if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) {\n                    SNAP(sourceX.start, sourceX.end, SURFX.start);\n                    snaps |= SNAP_LEFT;\n                } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) {\n                    SNAP(sourceX.end, sourceX.start, SURFX.end);\n                    snaps |= SNAP_RIGHT;\n                }\n            }\n        }\n    }\n\n    if (*SNAPMONITORGAP) {\n        const double GAPSIZE    = *SNAPMONITORGAP;\n        const auto   EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}};\n        const auto*  EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE;\n        const auto   MON        = DRAGGINGWINDOW->m_monitor.lock();\n\n        const auto*  GAPSOUT   = *SNAPRESPECTGAPS ? sc<CCssGapData*>(PGAPSOUT.ptr()->getData()) : &GAPSNONE;\n        const auto   WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved());\n\n        SRange       monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w};\n        SRange       monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h};\n\n        const bool   HAS_LEFT   = MON->m_reservedArea.left() > 0;\n        const bool   HAS_TOP    = MON->m_reservedArea.top() > 0;\n        const bool   HAS_BOTTOM = MON->m_reservedArea.bottom() > 0;\n        const bool   HAS_RIGHT  = MON->m_reservedArea.right() > 0;\n\n        if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) &&\n            ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) {\n            SNAP(sourceX.start, sourceX.end, monX.start);\n            snaps |= SNAP_LEFT;\n        }\n        if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) &&\n            ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) {\n            SNAP(sourceX.end, sourceX.start, monX.end);\n            snaps |= SNAP_RIGHT;\n        }\n        if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) &&\n            ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) {\n            SNAP(sourceY.start, sourceY.end, monY.start);\n            snaps |= SNAP_UP;\n        }\n        if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) &&\n            ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) {\n            SNAP(sourceY.end, sourceY.start, monY.end);\n            snaps |= SNAP_DOWN;\n        }\n    }\n\n    // remove extents from main surface\n    sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x};\n    sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y};\n\n    if (MODE == MBIND_RESIZE_FORCE_RATIO) {\n        if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) {\n            const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x);\n            if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT))\n                sourceY.start = sourceY.end - SIZEY;\n            else\n                sourceY.end = sourceY.start + SIZEY;\n        } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) {\n            const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y);\n            if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT))\n                sourceX.start = sourceX.end - SIZEX;\n            else\n                sourceX.end = sourceX.start + SIZEX;\n        }\n    }\n\n    sourcePos  = {sourceX.start, sourceY.start};\n    sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start};\n}\n\nvoid CLayoutManager::recalculateMonitor(PHLMONITOR m) {\n    if (m->m_activeSpecialWorkspace)\n        m->m_activeSpecialWorkspace->m_space->recalculate();\n    if (m->m_activeWorkspace)\n        m->m_activeWorkspace->m_space->recalculate();\n}\n\nvoid CLayoutManager::invalidateMonitorGeometries(PHLMONITOR m) {\n    for (const auto& ws : g_pCompositor->getWorkspaces()) {\n        if (ws && ws->m_monitor == m) {\n            ws->m_space->recheckWorkArea();\n            ws->m_space->recalculate();\n        }\n    }\n}\n"
  },
  {
    "path": "src/layout/LayoutManager.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n\n#include \"supplementary/DragController.hpp\"\n\n#include <optional>\n#include <expected>\n\nenum eFullscreenMode : int8_t;\n\nnamespace Layout {\n    class ITarget;\n    class CSpace;\n\n    enum eRectCorner : uint8_t {\n        CORNER_NONE        = 0,\n        CORNER_TOPLEFT     = (1 << 0),\n        CORNER_TOPRIGHT    = (1 << 1),\n        CORNER_BOTTOMRIGHT = (1 << 2),\n        CORNER_BOTTOMLEFT  = (1 << 3),\n    };\n\n    inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) {\n        const auto CENTER = box.middle();\n\n        if (pos.x < CENTER.x)\n            return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT;\n        return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT;\n    }\n\n    enum eSnapEdge : uint8_t {\n        SNAP_INVALID = 0,\n        SNAP_UP      = (1 << 0),\n        SNAP_DOWN    = (1 << 1),\n        SNAP_LEFT    = (1 << 2),\n        SNAP_RIGHT   = (1 << 3),\n    };\n\n    class CLayoutManager {\n      public:\n        CLayoutManager();\n        ~CLayoutManager() = default;\n\n        void                             newTarget(SP<ITarget> target, SP<CSpace> space);\n        void                             removeTarget(SP<ITarget> target);\n\n        void                             changeFloatingMode(SP<ITarget> target);\n\n        void                             beginDragTarget(SP<ITarget> target, eMouseBindMode mode);\n        void                             moveMouse(const Vector2D& mousePos);\n        void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        void                             moveTarget(const Vector2D& Δ, SP<ITarget> target);\n        void                             setTargetGeom(const CBox& box, SP<ITarget> target); // floats only\n        void                             endDragTarget();\n\n        std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n\n        void                             fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode);\n\n        void                             switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus = true);\n\n        void                             moveInDirection(SP<ITarget> target, const std::string& direction, bool silent = false);\n\n        SP<ITarget>                      getNextCandidate(SP<CSpace> space, SP<ITarget> from);\n\n        bool                             isReachable(SP<ITarget> target);\n\n        void                             bringTargetToTop(SP<ITarget> target);\n\n        std::optional<Vector2D>          predictSizeForNewTiledTarget();\n\n        void                             performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> target, eMouseBindMode mode, int corner, const Vector2D& beginSize);\n\n        void                             invalidateMonitorGeometries(PHLMONITOR);\n        void                             recalculateMonitor(PHLMONITOR);\n\n        const UP<Supplementary::CDragStateController>& dragController();\n\n      private:\n        UP<Supplementary::CDragStateController> m_dragStateController = makeUnique<Supplementary::CDragStateController>();\n    };\n}\n\ninline UP<Layout::CLayoutManager> g_layoutManager;"
  },
  {
    "path": "src/layout/algorithm/Algorithm.cpp",
    "content": "#include \"Algorithm.hpp\"\n\n#include \"FloatingAlgorithm.hpp\"\n#include \"TiledAlgorithm.hpp\"\n#include \"../target/WindowTarget.hpp\"\n#include \"../space/Space.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../desktop/history/WindowHistoryTracker.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../render/Renderer.hpp\"\n\n#include \"../../debug/log/Logger.hpp\"\n\nusing namespace Layout;\n\nSP<CAlgorithm> CAlgorithm::create(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space) {\n    auto algo                  = SP<CAlgorithm>(new CAlgorithm(std::move(tiled), std::move(floating), space));\n    algo->m_self               = algo;\n    algo->m_tiled->m_parent    = algo;\n    algo->m_floating->m_parent = algo;\n    return algo;\n}\n\nCAlgorithm::CAlgorithm(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space) :\n    m_tiled(std::move(tiled)), m_floating(std::move(floating)), m_space(space) {\n    ;\n}\n\nvoid CAlgorithm::addTarget(SP<ITarget> target) {\n    const bool SHOULD_FLOAT = target->floating();\n\n    if (SHOULD_FLOAT) {\n        m_floatingTargets.emplace_back(target);\n        m_floating->newTarget(target);\n    } else {\n        m_tiledTargets.emplace_back(target);\n        m_tiled->newTarget(target);\n    }\n}\n\nvoid CAlgorithm::removeTarget(SP<ITarget> target) {\n    const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target);\n\n    if (IS_FLOATING) {\n        std::erase(m_floatingTargets, target);\n        m_floating->removeTarget(target);\n        return;\n    }\n\n    const bool IS_TILED = std::ranges::contains(m_tiledTargets, target);\n\n    if (IS_TILED) {\n        std::erase(m_tiledTargets, target);\n        m_tiled->removeTarget(target);\n        return;\n    }\n\n    Log::logger->log(Log::ERR, \"BUG THIS: CAlgorithm::removeTarget, but not found\");\n}\n\nvoid CAlgorithm::moveTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint, bool reposition) {\n    const bool SHOULD_FLOAT = target->floating();\n\n    if (SHOULD_FLOAT) {\n        m_floatingTargets.emplace_back(target);\n        if (reposition)\n            m_floating->newTarget(target);\n        else\n            m_floating->movedTarget(target, focalPoint);\n    } else {\n        m_tiledTargets.emplace_back(target);\n        if (reposition)\n            m_tiled->newTarget(target);\n        else\n            m_tiled->movedTarget(target, focalPoint);\n    }\n}\n\nSP<CSpace> CAlgorithm::space() const {\n    return m_space.lock();\n}\n\nvoid CAlgorithm::setFloating(SP<ITarget> target, bool floating, bool reposition) {\n    removeTarget(target);\n\n    g_pHyprRenderer->damageWindow(target->window());\n\n    target->setFloating(floating);\n\n    moveTarget(target, std::nullopt, reposition);\n\n    g_pHyprRenderer->damageWindow(target->window());\n}\n\nsize_t CAlgorithm::tiledTargets() const {\n    return m_tiledTargets.size();\n}\n\nsize_t CAlgorithm::floatingTargets() const {\n    return m_floatingTargets.size();\n}\n\nvoid CAlgorithm::recalculate() {\n    m_tiled->recalculate();\n    m_floating->recalculate();\n\n    const auto PWORKSPACE = m_space->workspace();\n    if (!PWORKSPACE)\n        return;\n\n    const auto PMONITOR = PWORKSPACE->m_monitor;\n\n    if (PWORKSPACE->m_hasFullscreenWindow && PMONITOR) {\n        // massive hack from the fullscreen func\n        const auto PFULLWINDOW = PWORKSPACE->getFullscreenWindow();\n\n        if (PFULLWINDOW) {\n            if (PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) {\n                *PFULLWINDOW->m_realPosition = PMONITOR->m_position;\n                *PFULLWINDOW->m_realSize     = PMONITOR->m_size;\n            } else if (PWORKSPACE->m_fullscreenMode == FSMODE_MAXIMIZED)\n                PFULLWINDOW->layoutTarget()->setPositionGlobal(m_space->workArea());\n        }\n\n        return;\n    }\n}\n\nvoid CAlgorithm::recenter(SP<ITarget> t) {\n    if (t->floating())\n        m_floating->recenter(t);\n}\n\nstd::expected<void, std::string> CAlgorithm::layoutMsg(const std::string_view& sv) {\n    if (const auto ret = m_floating->layoutMsg(sv); !ret)\n        return ret;\n    return m_tiled->layoutMsg(sv);\n}\n\nstd::optional<Vector2D> CAlgorithm::predictSizeForNewTiledTarget() {\n    return m_tiled->predictSizeForNewTarget();\n}\n\nvoid CAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    if (target->floating())\n        m_floating->resizeTarget(Δ, target, corner);\n    else\n        m_tiled->resizeTarget(Δ, target, corner);\n}\n\nvoid CAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {\n    if (target->floating())\n        m_floating->moveTarget(Δ, target);\n}\n\nvoid CAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {\n    auto swapFirst = [&a, &b](std::vector<WP<ITarget>>& targets) -> bool {\n        auto ia = std::ranges::find(targets, a);\n        auto ib = std::ranges::find(targets, b);\n\n        if (ia != std::ranges::end(targets) && ib != std::ranges::end(targets)) {\n            std::iter_swap(ia, ib);\n            return true;\n        } else if (ia != std::ranges::end(targets))\n            *ia = b;\n        else if (ib != std::ranges::end(targets))\n            *ib = a;\n\n        return false;\n    };\n\n    if (!swapFirst(m_tiledTargets))\n        swapFirst(m_floatingTargets);\n\n    const WP<IModeAlgorithm> algA = a->floating() ? WP<IModeAlgorithm>(m_floating) : WP<IModeAlgorithm>(m_tiled);\n    const WP<IModeAlgorithm> algB = b->floating() ? WP<IModeAlgorithm>(m_floating) : WP<IModeAlgorithm>(m_tiled);\n\n    algA->swapTargets(a, b);\n    if (algA != algB)\n        algB->swapTargets(b, a);\n}\n\nvoid CAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    if (t->floating())\n        m_floating->moveTargetInDirection(t, dir, silent);\n    else\n        m_tiled->moveTargetInDirection(t, dir, silent);\n}\n\nvoid CAlgorithm::updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo) {\n    algo->m_parent = m_self;\n\n    for (const auto& t : m_floatingTargets) {\n        const auto TARGET = t.lock();\n        if (!TARGET)\n            continue;\n\n        // Unhide windows when switching layouts to prevent them from being permanently lost\n        const auto WINDOW = TARGET->window();\n        if (WINDOW)\n            WINDOW->setHidden(false);\n\n        m_floating->removeTarget(TARGET);\n        algo->newTarget(TARGET);\n    }\n\n    m_floating = std::move(algo);\n}\n\nvoid CAlgorithm::updateTiledAlgo(UP<ITiledAlgorithm>&& algo) {\n    algo->m_parent = m_self;\n\n    for (const auto& t : m_tiledTargets) {\n        const auto TARGET = t.lock();\n        if (!TARGET)\n            continue;\n\n        // Unhide windows when switching layouts to prevent them from being permanently lost\n        // This is a safeguard for layouts (including third-party plugins) that use setHidden\n        const auto WINDOW = TARGET->window();\n        if (WINDOW)\n            WINDOW->setHidden(false);\n\n        m_tiled->removeTarget(TARGET);\n        algo->newTarget(TARGET);\n    }\n\n    m_tiled = std::move(algo);\n}\n\nconst UP<ITiledAlgorithm>& CAlgorithm::tiledAlgo() const {\n    return m_tiled;\n}\n\nconst UP<IFloatingAlgorithm>& CAlgorithm::floatingAlgo() const {\n    return m_floating;\n}\n\nSP<ITarget> CAlgorithm::getNextCandidate(SP<ITarget> old) {\n    if (old->floating()) {\n        // use window history to determine best target\n        for (const auto& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) {\n            if (!w->m_workspace || w->m_workspace->m_space != m_space || !w->layoutTarget() || !w->layoutTarget()->space())\n                continue;\n\n            return w->layoutTarget();\n        }\n\n        // no history, fall back\n    } else {\n        // ask the layout\n        const auto CANDIDATE = m_tiled->getNextCandidate(old);\n        if (CANDIDATE)\n            return CANDIDATE;\n\n        // no candidate, fall back\n    }\n\n    // fallback: try to focus anything\n    if (!m_tiledTargets.empty())\n        return m_tiledTargets.back().lock();\n    if (!m_floatingTargets.empty())\n        return m_floatingTargets.back().lock();\n\n    // god damn it, maybe empty?\n    return nullptr;\n}\n\nvoid CAlgorithm::setTargetGeom(const CBox& box, SP<ITarget> target) {\n    if (!target->floating() || !std::ranges::contains(m_floatingTargets, target))\n        return;\n\n    m_floating->setTargetGeom(box, target);\n}\n"
  },
  {
    "path": "src/layout/algorithm/Algorithm.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/math/Direction.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n\n#include \"../LayoutManager.hpp\"\n\n#include <expected>\n#include <optional>\n\nnamespace Layout {\n    class ITarget;\n    class IFloatingAlgorithm;\n    class ITiledAlgorithm;\n    class CSpace;\n\n    class CAlgorithm {\n      public:\n        static SP<CAlgorithm> create(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space);\n        ~CAlgorithm() = default;\n\n        void                             addTarget(SP<ITarget> target);\n        void                             moveTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt, bool reposition = false);\n        void                             removeTarget(SP<ITarget> target);\n\n        void                             swapTargets(SP<ITarget> a, SP<ITarget> b);\n        void                             moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n        SP<ITarget>                      getNextCandidate(SP<ITarget> old);\n\n        void                             setFloating(SP<ITarget> target, bool floating, bool reposition = false);\n\n        std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n        std::optional<Vector2D>          predictSizeForNewTiledTarget();\n\n        void                             recalculate();\n        void                             recenter(SP<ITarget> t);\n\n        void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        void                             moveTarget(const Vector2D& Δ, SP<ITarget> target);\n\n        void                             setTargetGeom(const CBox& box, SP<ITarget> target); // only for float\n\n        void                             updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo);\n        void                             updateTiledAlgo(UP<ITiledAlgorithm>&& algo);\n\n        const UP<ITiledAlgorithm>&       tiledAlgo() const;\n        const UP<IFloatingAlgorithm>&    floatingAlgo() const;\n\n        SP<CSpace>                       space() const;\n\n        size_t                           tiledTargets() const;\n        size_t                           floatingTargets() const;\n\n      private:\n        CAlgorithm(UP<ITiledAlgorithm>&& tiled, UP<IFloatingAlgorithm>&& floating, SP<CSpace> space);\n\n        UP<ITiledAlgorithm>      m_tiled;\n        UP<IFloatingAlgorithm>   m_floating;\n        WP<CSpace>               m_space;\n        WP<CAlgorithm>           m_self;\n\n        std::vector<WP<ITarget>> m_tiledTargets, m_floatingTargets;\n    };\n}"
  },
  {
    "path": "src/layout/algorithm/FloatingAlgorithm.cpp",
    "content": "#include \"FloatingAlgorithm.hpp\"\n#include \"Algorithm.hpp\"\n#include \"../space/Space.hpp\"\n\nusing namespace Layout;\n\nvoid IFloatingAlgorithm::recalculate() {\n    ;\n}\n\nvoid IFloatingAlgorithm::recenter(SP<ITarget> t) {\n    const auto LAST = t->lastFloatingSize();\n\n    if (LAST.x <= 5 || LAST.y <= 5)\n        return;\n\n    t->setPositionGlobal({m_parent->space()->workArea().middle() - LAST / 2.F, LAST});\n}\n"
  },
  {
    "path": "src/layout/algorithm/FloatingAlgorithm.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n\n#include \"ModeAlgorithm.hpp\"\n\nnamespace Layout {\n\n    class ITarget;\n    class CAlgorithm;\n\n    class IFloatingAlgorithm : public IModeAlgorithm {\n      public:\n        virtual ~IFloatingAlgorithm() = default;\n\n        // a target is being moved by a delta\n        virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target) = 0;\n\n        // a target is moved to a pos x size\n        virtual void setTargetGeom(const CBox& geom, SP<ITarget> target) = 0;\n\n        virtual void recenter(SP<ITarget> t);\n\n        virtual void recalculate();\n\n      protected:\n        IFloatingAlgorithm() = default;\n\n        WP<CAlgorithm> m_parent;\n\n        friend class Layout::CAlgorithm;\n    };\n}"
  },
  {
    "path": "src/layout/algorithm/ModeAlgorithm.cpp",
    "content": "#include \"ModeAlgorithm.hpp\"\n\n#include \"../space/Space.hpp\"\n#include \"Algorithm.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n\nusing namespace Layout;\n\nstd::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_view& sv) {\n    return {};\n}\n\nstd::optional<Vector2D> IModeAlgorithm::predictSizeForNewTarget() {\n    return std::nullopt;\n}\n\nstd::optional<Vector2D> IModeAlgorithm::focalPointForDir(SP<ITarget> t, Math::eDirection dir) {\n    Vector2D   focalPoint;\n\n    const auto getFullscreenBB = [&]() -> std::optional<CBox> {\n        const auto PARENT = m_parent.lock();\n        if (!PARENT)\n            return std::nullopt;\n        const auto SPACE = PARENT->space();\n        if (!SPACE)\n            return std::nullopt;\n        const auto WS = SPACE->workspace();\n        if (!WS || !WS->m_monitor)\n            return std::nullopt;\n        return WS->m_monitor->logicalBox();\n    };\n\n    const auto WINDOWIDEALBB = t->fullscreenMode() != FSMODE_NONE ? getFullscreenBB().value_or(t->window()->getWindowIdealBoundingBoxIgnoreReserved()) :\n                                                                    t->window()->getWindowIdealBoundingBoxIgnoreReserved();\n\n    switch (dir) {\n        case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break;\n        case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break;\n        case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break;\n        case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break;\n        default: return std::nullopt;\n    }\n\n    return focalPoint;\n}\n"
  },
  {
    "path": "src/layout/algorithm/ModeAlgorithm.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/math/Direction.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n\n#include \"../LayoutManager.hpp\"\n\n#include <expected>\n\nnamespace Layout {\n\n    class ITarget;\n    class CAlgorithm;\n\n    class IModeAlgorithm {\n      public:\n        virtual ~IModeAlgorithm() = default;\n\n        // a completely new target\n        virtual void newTarget(SP<ITarget> target) = 0;\n\n        // a target moved into the algorithm (from another)\n        virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt) = 0;\n\n        // a target removed\n        virtual void removeTarget(SP<ITarget> target) = 0;\n\n        // a target is being resized by a delta. Corner none likely means not interactive\n        virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE) = 0;\n\n        // recalculate layout\n        virtual void recalculate() = 0;\n\n        // swap targets\n        virtual void swapTargets(SP<ITarget> a, SP<ITarget> b) = 0;\n\n        // move a target in a given direction\n        virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) = 0;\n\n        // optional: handle layout messages\n        virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n\n        // optional: predict new window's size\n        virtual std::optional<Vector2D> predictSizeForNewTarget();\n\n        // Impl'd here: focal point for dir\n        virtual std::optional<Vector2D> focalPointForDir(SP<ITarget> t, Math::eDirection dir);\n\n      protected:\n        IModeAlgorithm() = default;\n\n        WP<CAlgorithm> m_parent;\n\n        friend class Layout::CAlgorithm;\n    };\n}"
  },
  {
    "path": "src/layout/algorithm/TiledAlgorithm.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n\n#include \"ModeAlgorithm.hpp\"\n\nnamespace Layout {\n\n    class ITarget;\n    class CAlgorithm;\n\n    class ITiledAlgorithm : public IModeAlgorithm {\n      public:\n        virtual ~ITiledAlgorithm() = default;\n\n        virtual SP<ITarget> getNextCandidate(SP<ITarget> old) = 0;\n\n      protected:\n        ITiledAlgorithm() = default;\n\n        WP<CAlgorithm> m_parent;\n\n        friend class Layout::CAlgorithm;\n    };\n}"
  },
  {
    "path": "src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp",
    "content": "#include \"DefaultFloatingAlgorithm.hpp\"\n\n#include \"../../Algorithm.hpp\"\n\n#include \"../../../target/WindowTarget.hpp\"\n#include \"../../../space/Space.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../helpers/Monitor.hpp\"\n\nusing namespace Layout;\nusing namespace Layout::Floating;\n\nconstexpr const Vector2D DEFAULT_SIZE = {640, 400};\n\n//\nvoid CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) {\n    const auto WORK_AREA    = m_parent->space()->workArea(true);\n    const auto DESIRED_GEOM = target->desiredGeometry();\n    const auto MONITOR_POS  = m_parent->space()->workspace()->m_monitor->logicalBox().pos();\n\n    CBox       windowGeometry;\n\n    if (!DESIRED_GEOM) {\n        switch (DESIRED_GEOM.error()) {\n            case GEOMETRY_INVALID_DESIRED: {\n                // if the desired is invalid, we hide the window.\n                if (target->type() == TARGET_TYPE_WINDOW)\n                    dynamicPointerCast<CWindowTarget>(target)->window()->setHidden(true);\n                return;\n            }\n            case GEOMETRY_NO_DESIRED: {\n                // add a default geom\n                windowGeometry = CBox{WORK_AREA.middle() - DEFAULT_SIZE / 2.F, DEFAULT_SIZE};\n                break;\n            }\n        }\n    } else {\n        if (DESIRED_GEOM->pos)\n            windowGeometry = CBox{DESIRED_GEOM->pos.value(), DESIRED_GEOM->size};\n        else\n            windowGeometry = CBox{WORK_AREA.middle() - DESIRED_GEOM->size / 2.F, DESIRED_GEOM->size};\n    }\n\n    bool posOverridden = false;\n\n    if (target->window() && target->window()->m_firstMap) {\n        const auto WINDOW = target->window();\n\n        // set this here so that expressions can use it. This could be wrong of course.\n        WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE);\n\n        if (!WINDOW->m_ruleApplicator->static_.size.empty()) {\n            const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size);\n            if (!COMPUTED)\n                Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", WINDOW->m_ruleApplicator->static_.size);\n            else {\n                windowGeometry.w = COMPUTED->x;\n                windowGeometry.h = COMPUTED->y;\n\n                // update for pos to work with size.\n                WINDOW->m_realPosition->setValueAndWarp(*COMPUTED);\n            }\n        }\n\n        if (!WINDOW->m_ruleApplicator->static_.position.empty()) {\n            const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position);\n            if (!COMPUTED)\n                Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", WINDOW->m_ruleApplicator->static_.position);\n            else {\n                windowGeometry.x = COMPUTED->x + MONITOR_POS.x;\n                windowGeometry.y = COMPUTED->y + MONITOR_POS.y;\n                posOverridden    = true;\n            }\n        }\n\n        if (WINDOW->m_ruleApplicator->static_.center.value_or(false)) {\n            const auto POS   = WORK_AREA.middle() - windowGeometry.size() / 2.f;\n            windowGeometry.x = POS.x;\n            windowGeometry.y = POS.y;\n            posOverridden    = true;\n        }\n    } else if (target->lastFloatingSize().x > 5 && target->lastFloatingSize().y > 5) {\n        windowGeometry.w = target->lastFloatingSize().x;\n        windowGeometry.h = target->lastFloatingSize().y;\n    }\n\n    if (!posOverridden && (!DESIRED_GEOM || !DESIRED_GEOM->pos))\n        windowGeometry = CBox{WORK_AREA.middle() - windowGeometry.size() / 2.F, windowGeometry.size()};\n\n    if (posOverridden                                                                           // pos is overridden by a rule\n        || (DESIRED_GEOM && DESIRED_GEOM->pos && target->window() && target->window()->m_isX11) // X11 window with a geom\n        || WORK_AREA.containsPoint(windowGeometry.middle()))                                    // geometry within work area\n        target->setPositionGlobal(windowGeometry);\n    else {\n        const auto POS   = WORK_AREA.middle() - windowGeometry.size() / 2.f;\n        windowGeometry.x = POS.x;\n        windowGeometry.y = POS.y;\n\n        target->setPositionGlobal(windowGeometry);\n    }\n\n    // TODO: not very OOP, is it?\n    if (const auto WTARGET = dynamicPointerCast<CWindowTarget>(target); WTARGET) {\n        const auto PWINDOW = WTARGET->window();\n\n        if (PWINDOW->m_X11DoesntWantBorders || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect())) {\n            PWINDOW->m_realPosition->warp();\n            PWINDOW->m_realSize->warp();\n        }\n\n        if (!PWINDOW->isX11OverrideRedirect())\n            g_pCompositor->changeWindowZOrder(PWINDOW, true);\n        else {\n            PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal();\n            PWINDOW->m_reportedSize        = PWINDOW->m_pendingReportedSize;\n        }\n    }\n\n    updateTarget(target);\n}\n\nvoid CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {\n    auto       LAST_SIZE    = target->lastFloatingSize();\n    const auto CURRENT_SIZE = target->position().size();\n\n    // ignore positioning a dragged target\n    if (g_layoutManager->dragController()->target() == target)\n        return;\n\n    if (LAST_SIZE.x < 5 || LAST_SIZE.y < 5) {\n        const auto DESIRED = target->desiredGeometry();\n        LAST_SIZE          = DESIRED ? DESIRED->size : DEFAULT_SIZE;\n    }\n\n    if (target->wasTiling()) {\n        // Avoid floating toggles that don't change size, they aren't easily visible to the user\n        if (std::abs(LAST_SIZE.x - CURRENT_SIZE.x) < 5 && std::abs(LAST_SIZE.y - CURRENT_SIZE.y) < 5)\n            LAST_SIZE += Vector2D{10, 10};\n\n        // calculate new position\n        const auto OLD_CENTER = target->position().middle();\n\n        // put around the current center, fit in workArea\n        target->setPositionGlobal(fitBoxInWorkArea(CBox{OLD_CENTER - LAST_SIZE / 2.F, LAST_SIZE}, target));\n\n    } else {\n        // calculate new position\n        const auto THIS_MON_POS = m_parent->space()->workspace()->m_monitor->m_position;\n        const auto OLD_POS      = target->position().pos();\n        const auto MON_FROM_OLD = g_pCompositor->getMonitorFromVector(OLD_POS);\n        const auto NEW_POS      = MON_FROM_OLD ? OLD_POS - MON_FROM_OLD->m_position + THIS_MON_POS : OLD_POS;\n\n        // put around the current center, fit in workArea\n        target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target));\n    }\n\n    updateTarget(target);\n}\n\nCBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t) {\n    const auto WORK_AREA = m_parent->space()->workArea(true);\n    const auto EXTENTS   = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS) : SBoxExtents{};\n    CBox       targetBox = box.copy().addExtents(EXTENTS);\n\n    targetBox.x = std::max(targetBox.x, WORK_AREA.x);\n    targetBox.y = std::max(targetBox.y, WORK_AREA.y);\n\n    if (targetBox.x + targetBox.w > WORK_AREA.x + WORK_AREA.w)\n        targetBox.x = WORK_AREA.x + WORK_AREA.w - targetBox.w;\n\n    if (targetBox.y + targetBox.h > WORK_AREA.y + WORK_AREA.h)\n        targetBox.y = WORK_AREA.y + WORK_AREA.h - targetBox.h;\n\n    return targetBox.addExtents(SBoxExtents{.topLeft = -EXTENTS.topLeft, .bottomRight = -EXTENTS.bottomRight});\n}\n\nvoid CDefaultFloatingAlgorithm::removeTarget(SP<ITarget> target) {\n    target->rememberFloatingSize(target->position().size());\n    m_datas.erase(target);\n}\n\nvoid CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    auto pos = target->position();\n    pos.w += Δ.x;\n    pos.h += Δ.y;\n    pos.translate(-Δ / 2.F);\n    target->setPositionGlobal(pos);\n\n    if (g_layoutManager->dragController()->target() == target)\n        target->warpPositionSize();\n\n    updateTarget(target);\n}\n\nvoid CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {\n    auto pos = target->position();\n    pos.translate(Δ);\n    target->setPositionGlobal(pos);\n\n    if (g_layoutManager->dragController()->target() == target)\n        target->warpPositionSize();\n\n    updateTarget(target);\n}\n\nvoid CDefaultFloatingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {\n    auto posABackup = a->position();\n    a->setPositionGlobal(b->position());\n    b->setPositionGlobal(posABackup);\n\n    updateTarget(a);\n    updateTarget(b);\n}\n\nvoid CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    auto       pos  = t->position();\n    auto       work = m_parent->space()->workArea(true);\n\n    const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS) : SBoxExtents{};\n\n    switch (dir) {\n        case Math::DIRECTION_LEFT: pos.x = work.x + EXTENTS.topLeft.x; break;\n        case Math::DIRECTION_RIGHT: pos.x = work.x + work.w - pos.w - EXTENTS.bottomRight.x; break;\n        case Math::DIRECTION_UP: pos.y = work.y + EXTENTS.topLeft.y; break;\n        case Math::DIRECTION_DOWN: pos.y = work.y + work.h - pos.h - EXTENTS.bottomRight.y; break;\n        default: Log::logger->log(Log::ERR, \"Invalid direction in CDefaultFloatingAlgorithm::moveTargetInDirection\"); break;\n    }\n\n    t->setPositionGlobal(pos);\n\n    updateTarget(t);\n}\n\nvoid CDefaultFloatingAlgorithm::recenter(SP<ITarget> t) {\n    if (!m_datas.contains(t)) {\n        IFloatingAlgorithm::recenter(t);\n        return;\n    }\n\n    t->setPositionGlobal(m_datas.at(t).lastBox);\n}\n\nvoid CDefaultFloatingAlgorithm::setTargetGeom(const CBox& geom, SP<ITarget> target) {\n    target->setPositionGlobal(geom);\n\n    updateTarget(target);\n}\n\nvoid CDefaultFloatingAlgorithm::updateTarget(SP<ITarget> t) {\n    m_datas[t] = {.lastBox = t->position()};\n}\n"
  },
  {
    "path": "src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp",
    "content": "#include \"../../FloatingAlgorithm.hpp\"\n\n#include <map>\n\nnamespace Layout {\n    class CAlgorithm;\n}\n\nnamespace Layout::Floating {\n    class CDefaultFloatingAlgorithm : public IFloatingAlgorithm {\n      public:\n        CDefaultFloatingAlgorithm()          = default;\n        virtual ~CDefaultFloatingAlgorithm() = default;\n\n        virtual void newTarget(SP<ITarget> target);\n        virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual void removeTarget(SP<ITarget> target);\n\n        virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target);\n\n        virtual void setTargetGeom(const CBox& geom, SP<ITarget> target);\n\n        virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);\n        virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n        virtual void recenter(SP<ITarget> t);\n\n      private:\n        CBox fitBoxInWorkArea(const CBox& box, SP<ITarget> t);\n\n        void updateTarget(SP<ITarget>);\n\n        struct SWindowData {\n            CBox lastBox;\n        };\n\n        std::map<WP<ITarget>, SWindowData> m_datas;\n    };\n};"
  },
  {
    "path": "src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp",
    "content": "#include \"DwindleAlgorithm.hpp\"\n\n#include \"../../Algorithm.hpp\"\n#include \"../../../space/Space.hpp\"\n#include \"../../../target/WindowTarget.hpp\"\n#include \"../../../LayoutManager.hpp\"\n\n#include \"../../../../config/ConfigValue.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../helpers/Monitor.hpp\"\n#include \"../../../../Compositor.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Layout;\nusing namespace Layout::Tiled;\n\nstruct Layout::Tiled::SDwindleNodeData {\n    WP<SDwindleNodeData>                pParent;\n    bool                                isNode = false;\n    WP<ITarget>                         pTarget;\n    std::array<WP<SDwindleNodeData>, 2> children = {};\n    WP<SDwindleNodeData>                self;\n    bool                                splitTop               = false; // for preserve_split\n    CBox                                box                    = {0};\n    float                               splitRatio             = 1.f;\n    bool                                valid                  = true;\n    bool                                ignoreFullscreenChecks = false;\n\n    // For list lookup\n    bool operator==(const SDwindleNodeData& rhs) const {\n        return pTarget.lock() == rhs.pTarget.lock() && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && children[1] == rhs.children[1];\n    }\n\n    void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false) {\n        if (children[0]) {\n            static auto PSMARTSPLIT       = CConfigValue<Hyprlang::INT>(\"dwindle:smart_split\");\n            static auto PPRESERVESPLIT    = CConfigValue<Hyprlang::INT>(\"dwindle:preserve_split\");\n            static auto PFLMULT           = CConfigValue<Hyprlang::FLOAT>(\"dwindle:split_width_multiplier\");\n            static auto PPRECISEMOUSEMOVE = CConfigValue<Hyprlang::INT>(\"dwindle:precise_mouse_move\");\n\n            if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0 && *PPRECISEMOUSEMOVE == 0)\n                splitTop = box.h * *PFLMULT > box.w;\n\n            if (verticalOverride)\n                splitTop = true;\n            else if (horizontalOverride)\n                splitTop = false;\n\n            const auto SPLITSIDE = !splitTop;\n\n            if (SPLITSIDE) {\n                // split left/right\n                const float FIRSTSIZE = box.w / 2.0 * splitRatio;\n                children[0]->box      = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize();\n                children[1]->box      = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize();\n            } else {\n                // split top/bottom\n                const float FIRSTSIZE = box.h / 2.0 * splitRatio;\n                children[0]->box      = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize();\n                children[1]->box      = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize();\n            }\n\n            children[0]->recalcSizePosRecursive(force);\n            children[1]->recalcSizePosRecursive(force);\n        } else\n            pTarget->setPositionGlobal(box);\n    }\n};\n\nvoid CDwindleAlgorithm::newTarget(SP<ITarget> target) {\n    addTarget(target);\n}\n\nvoid CDwindleAlgorithm::addTarget(SP<ITarget> target) {\n    const auto WORK_AREA = m_parent->space()->workArea();\n\n    const auto PNODE = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());\n    PNODE->self      = PNODE;\n\n    const auto  PMONITOR   = m_parent->space()->workspace()->m_monitor;\n    const auto  PWORKSPACE = m_parent->space()->workspace();\n\n    static auto PUSEACTIVE    = CConfigValue<Hyprlang::INT>(\"dwindle:use_active_for_splits\");\n    static auto PDEFAULTSPLIT = CConfigValue<Hyprlang::FLOAT>(\"dwindle:default_split_ratio\");\n\n    // Populate the node with our window's data\n    PNODE->pTarget = target;\n    PNODE->isNode  = false;\n\n    SP<SDwindleNodeData> OPENINGON;\n\n    const auto           MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal());\n    const auto           ACTIVE_MON  = Desktop::focusState()->monitor();\n\n    if ((PWORKSPACE == ACTIVE_MON->m_activeWorkspace || (PWORKSPACE->m_isSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) {\n        OPENINGON = getNodeFromWindow(\n            g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY));\n\n        if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))\n            OPENINGON = getClosestNode(MOUSECOORDS);\n\n    } else if (*PUSEACTIVE || m_overrideFocalPoint) {\n        const auto ACTIVE_WINDOW = Desktop::focusState()->window();\n\n        if (m_overrideFocalPoint)\n            OPENINGON = getClosestNode(*m_overrideFocalPoint);\n        else if (!m_overrideFocalPoint && ACTIVE_WINDOW && !ACTIVE_WINDOW->m_isFloating && ACTIVE_WINDOW != target->window() && ACTIVE_WINDOW->m_workspace == PWORKSPACE &&\n                 ACTIVE_WINDOW->m_isMapped)\n            OPENINGON = getNodeFromWindow(ACTIVE_WINDOW);\n\n        if (!OPENINGON)\n            OPENINGON = getClosestNode(MOUSECOORDS, target);\n    } else\n        OPENINGON = getFirstNode();\n\n    // first, check if OPENINGON isn't too big.\n    const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size;\n    if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) {\n        // we can't continue. make it floating.\n        std::erase(m_dwindleNodesData, PNODE);\n        m_parent->setFloating(target, true, true);\n        return;\n    }\n\n    // last fail-safe to avoid duplicate fullscreens\n    if ((!OPENINGON || OPENINGON->pTarget.lock() == target) && getNodes() > 1) {\n        for (auto& node : m_dwindleNodesData) {\n            if (node->pTarget.lock() && node->pTarget.lock() != target) {\n                OPENINGON = node;\n                break;\n            }\n        }\n    }\n\n    // if it's the first, it's easy. Make it fullscreen.\n    if (!OPENINGON || OPENINGON->pTarget.lock() == target) {\n        PNODE->box = WORK_AREA;\n        PNODE->pTarget->setPositionGlobal(PNODE->box);\n        return;\n    }\n\n    // get the node under our cursor\n\n    const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());\n\n    // make the parent have the OPENINGON's stats\n    NEWPARENT->box        = OPENINGON->box;\n    NEWPARENT->pParent    = OPENINGON->pParent;\n    NEWPARENT->isNode     = true; // it is a node\n    NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1F, 1.9F);\n\n    static auto PWIDTHMULTIPLIER = CConfigValue<Hyprlang::FLOAT>(\"dwindle:split_width_multiplier\");\n\n    // if cursor over first child, make it first, etc\n    const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER;\n    NEWPARENT->splitTop   = !SIDEBYSIDE;\n\n    static auto PFORCESPLIT                = CConfigValue<Hyprlang::INT>(\"dwindle:force_split\");\n    static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue<Hyprlang::INT>(\"dwindle:permanent_direction_override\");\n    static auto PSMARTSPLIT                = CConfigValue<Hyprlang::INT>(\"dwindle:smart_split\");\n    static auto PSPLITBIAS                 = CConfigValue<Hyprlang::INT>(\"dwindle:split_bias\");\n    static auto PPRECISEMOUSEMOVE          = CConfigValue<Hyprlang::INT>(\"dwindle:precise_mouse_move\");\n\n    bool        horizontalOverride = false;\n    bool        verticalOverride   = false;\n\n    // let user select position -> top, right, bottom, left\n    if (m_overrideDirection != Math::DIRECTION_DEFAULT) {\n\n        // this is horizontal\n        if (m_overrideDirection % 2 == 0)\n            verticalOverride = true;\n        else\n            horizontalOverride = true;\n\n        // 0 -> top and left | 1,2 -> right and bottom\n        if (m_overrideDirection % 3 == 0) {\n            NEWPARENT->children[1] = OPENINGON;\n            NEWPARENT->children[0] = PNODE;\n        } else {\n            NEWPARENT->children[0] = OPENINGON;\n            NEWPARENT->children[1] = PNODE;\n        }\n\n        // whether or not the override persists after opening one window\n        if (*PERMANENTDIRECTIONOVERRIDE == 0)\n            m_overrideDirection = Math::DIRECTION_DEFAULT;\n    } else if (*PSMARTSPLIT == 1 || (*PPRECISEMOUSEMOVE == 1 && g_layoutManager->dragController()->wasDraggingWindow())) {\n        const auto PARENT_CENTER      = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2;\n        const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w;\n        const auto DELTA              = MOUSECOORDS - PARENT_CENTER;\n        const auto DELTA_SLOPE        = DELTA.y / DELTA.x;\n\n        if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) {\n            if (DELTA.x > 0) {\n                // right\n                NEWPARENT->splitTop    = false;\n                NEWPARENT->children[0] = OPENINGON;\n                NEWPARENT->children[1] = PNODE;\n            } else {\n                // left\n                NEWPARENT->splitTop    = false;\n                NEWPARENT->children[0] = PNODE;\n                NEWPARENT->children[1] = OPENINGON;\n            }\n        } else {\n            if (DELTA.y > 0) {\n                // bottom\n                NEWPARENT->splitTop    = true;\n                NEWPARENT->children[0] = OPENINGON;\n                NEWPARENT->children[1] = PNODE;\n            } else {\n                // top\n                NEWPARENT->splitTop    = true;\n                NEWPARENT->children[0] = PNODE;\n                NEWPARENT->children[1] = OPENINGON;\n            }\n        }\n    } else if (*PFORCESPLIT == 0 || m_overrideFocalPoint) {\n        if ((SIDEBYSIDE && MOUSECOORDS.x < NEWPARENT->box.x + (NEWPARENT->box.w / 2.F)) || (!SIDEBYSIDE && MOUSECOORDS.y < NEWPARENT->box.y + (NEWPARENT->box.h / 2.F))) {\n            // we are hovering over the first node, make PNODE first.\n            NEWPARENT->children[1] = OPENINGON;\n            NEWPARENT->children[0] = PNODE;\n        } else {\n            // we are hovering over the second node, make PNODE second.\n            NEWPARENT->children[0] = OPENINGON;\n            NEWPARENT->children[1] = PNODE;\n        }\n    } else if (*PFORCESPLIT == 1) {\n        NEWPARENT->children[1] = OPENINGON;\n        NEWPARENT->children[0] = PNODE;\n    } else {\n        NEWPARENT->children[0] = OPENINGON;\n        NEWPARENT->children[1] = PNODE;\n    }\n\n    // split in favor of a specific window\n    if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE)\n        NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio;\n\n    // and update the previous parent if it exists\n    if (OPENINGON->pParent) {\n        if (OPENINGON->pParent->children[0] == OPENINGON)\n            OPENINGON->pParent->children[0] = NEWPARENT;\n        else\n            OPENINGON->pParent->children[1] = NEWPARENT;\n    }\n\n    // Update the children\n    if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) {\n        // split left/right -> forced\n        OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};\n        PNODE->box     = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};\n    } else {\n        // split top/bottom\n        OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};\n        PNODE->box     = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};\n    }\n\n    OPENINGON->pParent = NEWPARENT;\n    PNODE->pParent     = NEWPARENT;\n\n    NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride);\n\n    calculateWorkspace();\n}\n\nvoid CDwindleAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {\n    m_overrideFocalPoint = focalPoint;\n    addTarget(target);\n    m_overrideFocalPoint.reset();\n}\n\nvoid CDwindleAlgorithm::removeTarget(SP<ITarget> target) {\n    const auto PNODE = getNodeFromTarget(target);\n\n    if (!PNODE) {\n        Log::logger->log(Log::ERR, \"onWindowRemovedTiling node null?\");\n        return;\n    }\n\n    if (target->fullscreenMode() != FSMODE_NONE)\n        g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE);\n\n    const auto PPARENT = PNODE->pParent;\n\n    if (!PPARENT) {\n        Log::logger->log(Log::DEBUG, \"Removing last node (dwindle)\");\n        std::erase(m_dwindleNodesData, PNODE);\n        return;\n    }\n\n    const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0];\n\n    PSIBLING->pParent = PPARENT->pParent;\n\n    if (PPARENT->pParent != nullptr) {\n        if (PPARENT->pParent->children[0] == PPARENT)\n            PPARENT->pParent->children[0] = PSIBLING;\n        else\n            PPARENT->pParent->children[1] = PSIBLING;\n    }\n\n    PPARENT->valid = false;\n    PNODE->valid   = false;\n\n    std::erase(m_dwindleNodesData, PPARENT);\n    std::erase(m_dwindleNodesData, PNODE);\n\n    recalculate();\n}\n\nvoid CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    if (!validMapped(target->window()))\n        return;\n\n    const auto PNODE = getNodeFromTarget(target);\n\n    if (!PNODE)\n        return;\n\n    static auto PANIMATE       = CConfigValue<Hyprlang::INT>(\"misc:animate_manual_resizes\");\n    static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>(\"dwindle:smart_resizing\");\n\n    // get some data about our window\n    const auto PMONITOR         = m_parent->space()->workspace()->m_monitor;\n    const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved();\n    const auto BOX              = target->position();\n    const bool DISPLAYLEFT      = STICKS(BOX.x, MONITOR_WORKAREA.x);\n    const bool DISPLAYRIGHT     = STICKS(BOX.x + BOX.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);\n    const bool DISPLAYTOP       = STICKS(BOX.y, MONITOR_WORKAREA.y);\n    const bool DISPLAYBOTTOM    = STICKS(BOX.y + BOX.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);\n\n    // construct allowed movement\n    Vector2D allowedMovement = Δ;\n    if (DISPLAYLEFT && DISPLAYRIGHT)\n        allowedMovement.x = 0;\n\n    if (DISPLAYBOTTOM && DISPLAYTOP)\n        allowedMovement.y = 0;\n\n    if (*PSMARTRESIZING == 1) {\n        // Identify inner and outer nodes for both directions\n        SP<SDwindleNodeData> PVOUTER = nullptr;\n        SP<SDwindleNodeData> PVINNER = nullptr;\n        SP<SDwindleNodeData> PHOUTER = nullptr;\n        SP<SDwindleNodeData> PHINNER = nullptr;\n\n        const auto           LEFT   = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT;\n        const auto           TOP    = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM;\n        const auto           RIGHT  = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT;\n        const auto           BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP;\n        const auto           NONE   = corner == CORNER_NONE;\n\n        for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) {\n            const auto PPARENT = PCURRENT->pParent;\n\n            if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT)))\n                PVOUTER = PCURRENT;\n            else if (!PVOUTER && !PVINNER && PPARENT->splitTop)\n                PVINNER = PCURRENT;\n            else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT)))\n                PHOUTER = PCURRENT;\n            else if (!PHOUTER && !PHINNER && !PPARENT->splitTop)\n                PHINNER = PCURRENT;\n\n            if (PVOUTER && PHOUTER)\n                break;\n        }\n\n        if (PHOUTER) {\n            PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9);\n\n            if (PHINNER) {\n                const auto ORIGINAL = PHINNER->box.w;\n                PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);\n                if (PHINNER->pParent->children[0] == PHINNER)\n                    PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);\n                else\n                    PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);\n                PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);\n            } else\n                PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);\n        }\n\n        if (PVOUTER) {\n            PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9);\n\n            if (PVINNER) {\n                const auto ORIGINAL = PVINNER->box.h;\n                PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);\n                if (PVINNER->pParent->children[0] == PVINNER)\n                    PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);\n                else\n                    PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);\n                PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);\n            } else\n                PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);\n        }\n    } else {\n        // get the correct containers to apply splitratio to\n        const auto PPARENT = PNODE->pParent;\n\n        if (!PPARENT)\n            return; // the only window on a workspace, ignore\n\n        const bool PARENTSIDEBYSIDE = !PPARENT->splitTop;\n\n        // Get the parent's parent\n        auto                          PPARENT2 = PPARENT->pParent;\n\n        Hyprutils::Utils::CScopeGuard x([target, this] {\n            // snap all windows, don't animate resizes if they are manual\n            if (target == g_layoutManager->dragController()->target()) {\n                for (const auto& w : m_dwindleNodesData) {\n                    if (w->isNode)\n                        continue;\n\n                    w->pTarget->warpPositionSize();\n                }\n            }\n        });\n\n        // No parent means we have only 2 windows, and thus one axis of freedom\n        if (!PPARENT2) {\n            if (PARENTSIDEBYSIDE) {\n                allowedMovement.x *= 2.f / PPARENT->box.w;\n                PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);\n                PPARENT->recalcSizePosRecursive(*PANIMATE == 0);\n            } else {\n                allowedMovement.y *= 2.f / PPARENT->box.h;\n                PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);\n                PPARENT->recalcSizePosRecursive(*PANIMATE == 0);\n            }\n\n            return;\n        }\n\n        // Get first parent with other split\n        while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE)\n            PPARENT2 = PPARENT2->pParent;\n\n        // no parent, one axis of freedom\n        if (!PPARENT2) {\n            if (PARENTSIDEBYSIDE) {\n                allowedMovement.x *= 2.f / PPARENT->box.w;\n                PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);\n                PPARENT->recalcSizePosRecursive(*PANIMATE == 0);\n            } else {\n                allowedMovement.y *= 2.f / PPARENT->box.h;\n                PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);\n                PPARENT->recalcSizePosRecursive(*PANIMATE == 0);\n            }\n\n            return;\n        }\n\n        // 2 axes of freedom\n        const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2;\n        const auto TOPCONTAINER  = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT;\n\n        allowedMovement.x *= 2.f / SIDECONTAINER->box.w;\n        allowedMovement.y *= 2.f / TOPCONTAINER->box.h;\n\n        SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9);\n        TOPCONTAINER->splitRatio  = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9);\n        SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0);\n        TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0);\n    }\n\n    // snap all windows, don't animate resizes if they are manual\n    if (target == g_layoutManager->dragController()->target()) {\n        for (const auto& w : m_dwindleNodesData) {\n            if (w->isNode)\n                continue;\n\n            w->pTarget->warpPositionSize();\n        }\n    }\n}\n\nSP<ITarget> CDwindleAlgorithm::getNextCandidate(SP<ITarget> old) {\n    const auto MIDDLE = old->position().middle();\n\n    if (const auto NODE = getClosestNode(MIDDLE); NODE)\n        return NODE->pTarget.lock();\n\n    if (const auto NODE = getFirstNode(); NODE)\n        return NODE->pTarget.lock();\n\n    return nullptr;\n}\n\nvoid CDwindleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {\n    auto nodeA = getNodeFromTarget(a);\n    auto nodeB = getNodeFromTarget(b);\n\n    if (nodeA)\n        nodeA->pTarget = b;\n    if (nodeB)\n        nodeB->pTarget = a;\n}\n\nvoid CDwindleAlgorithm::recalculate() {\n    calculateWorkspace();\n}\n\nstd::optional<Vector2D> CDwindleAlgorithm::predictSizeForNewTarget() {\n    // get window candidate\n    PHLWINDOW candidate = Desktop::focusState()->window();\n\n    if (!candidate || candidate->m_workspace != m_parent->space()->workspace())\n        candidate = m_parent->space()->workspace()->getFirstWindow();\n\n    // create a fake node\n    SDwindleNodeData node;\n\n    if (!candidate)\n        return Desktop::focusState()->monitor()->m_size;\n    else {\n        const auto PNODE = getNodeFromWindow(candidate);\n\n        if (!PNODE)\n            return {};\n\n        node = *PNODE;\n        node.pTarget.reset();\n\n        CBox        box = PNODE->box;\n\n        static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>(\"dwindle:split_width_multiplier\");\n\n        bool        splitTop = box.h * *PFLMULT > box.w;\n\n        const auto  SPLITSIDE = !splitTop;\n\n        if (SPLITSIDE)\n            node.box = {{}, {box.w / 2.0, box.h}};\n        else\n            node.box = {{}, {box.w, box.h / 2.0}};\n\n        // TODO: make this better and more accurate\n\n        return node.box.size();\n    }\n\n    return {};\n}\n\nvoid CDwindleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    static auto    PMONITORFALLBACK = CConfigValue<Hyprlang::INT>(\"binds:window_direction_monitor_fallback\");\n\n    const auto     PNODE       = getNodeFromTarget(t);\n    const Vector2D originalPos = t->position().middle();\n\n    if (!PNODE || !t->window())\n        return;\n\n    const auto FOCAL_POINT = focalPointForDir(t, dir);\n\n    const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(FOCAL_POINT.value_or(t->position().middle()));\n\n    if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor && !*PMONITORFALLBACK)\n        return; // noop\n\n    t->window()->setAnimationsToMove();\n\n    removeTarget(t);\n\n    if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) {\n        // move with a focal point\n\n        if (PMONITORFOCAL->m_activeWorkspace)\n            t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space, FOCAL_POINT);\n\n        return;\n    }\n\n    movedTarget(t, FOCAL_POINT);\n\n    // restore focus to the previous position\n    if (silent) {\n        const auto PNODETOFOCUS = getClosestNode(originalPos);\n        if (PNODETOFOCUS && PNODETOFOCUS->pTarget)\n            Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pTarget->window(), Desktop::FOCUS_REASON_KEYBIND);\n    }\n}\n\n// --------- internal --------- //\n\nvoid CDwindleAlgorithm::calculateWorkspace() {\n    const auto PWORKSPACE = m_parent->space()->workspace();\n    const auto PMONITOR   = PWORKSPACE->m_monitor;\n\n    if (!PMONITOR || PWORKSPACE->m_hasFullscreenWindow)\n        return;\n\n    const auto TOPNODE = getMasterNode();\n\n    if (TOPNODE) {\n        TOPNODE->box = m_parent->space()->workArea();\n        TOPNODE->recalcSizePosRecursive();\n    }\n}\n\nSP<SDwindleNodeData> CDwindleAlgorithm::getNodeFromTarget(SP<ITarget> t) {\n    for (const auto& n : m_dwindleNodesData) {\n        if (n->pTarget == t)\n            return n;\n    }\n\n    return nullptr;\n}\n\nSP<SDwindleNodeData> CDwindleAlgorithm::getNodeFromWindow(PHLWINDOW w) {\n    return w ? getNodeFromTarget(w->layoutTarget()) : nullptr;\n}\n\nint CDwindleAlgorithm::getNodes() {\n    return m_dwindleNodesData.size();\n}\n\nSP<SDwindleNodeData> CDwindleAlgorithm::getFirstNode() {\n    return m_dwindleNodesData.empty() ? nullptr : m_dwindleNodesData.at(0);\n}\n\nSP<SDwindleNodeData> CDwindleAlgorithm::getClosestNode(const Vector2D& point, SP<ITarget> skip) {\n    SP<SDwindleNodeData> res         = nullptr;\n    double               distClosest = -1;\n    for (auto& n : m_dwindleNodesData) {\n        if (skip && n->pTarget == skip)\n            continue;\n\n        if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) {\n            auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size());\n            if (!res || distAnother < distClosest) {\n                res         = n;\n                distClosest = distAnother;\n            }\n        }\n    }\n    return res;\n}\n\nSP<SDwindleNodeData> CDwindleAlgorithm::getMasterNode() {\n    for (auto& n : m_dwindleNodesData) {\n        if (!n->pParent)\n            return n;\n    }\n    return nullptr;\n}\n\nstd::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_view& sv) {\n    const auto ARGS = CVarList2(std::string{sv}, 0, ' ');\n\n    const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window());\n\n    if (ARGS[0] == \"togglesplit\") {\n        if (CURRENT_NODE) {\n            if (!toggleSplit(CURRENT_NODE))\n                return std::unexpected(\"can't togglesplit in the current workspace\");\n        }\n    } else if (ARGS[0] == \"swapsplit\") {\n        if (CURRENT_NODE) {\n            if (!swapSplit(CURRENT_NODE))\n                return std::unexpected(\"can't swapsplit in the current workspace\");\n        }\n    } else if (ARGS[0] == \"rotatesplit\") {\n        if (CURRENT_NODE) {\n            int angle = 90;\n            if (!ARGS[1].empty()) {\n                try {\n                    angle = std::stoi(std::string{ARGS[1]});\n                } catch (const std::exception& e) {\n                    Log::logger->log(Log::WARN, \"Invalid angle argument for rotatesplit: {}\", ARGS[1]);\n                    return std::unexpected(\"Invalid angle argument\");\n                }\n            }\n            rotateSplit(CURRENT_NODE, angle);\n        }\n    } else if (ARGS[0] == \"movetoroot\") {\n        auto node = CURRENT_NODE;\n        if (!ARGS[1].empty()) {\n            auto w = g_pCompositor->getWindowByRegex(std::string{ARGS[1]});\n            if (w)\n                node = getNodeFromWindow(w);\n        }\n\n        const auto STABLE = ARGS[2].empty() || ARGS[2] != \"unstable\";\n        if (!moveToRoot(node, STABLE))\n            return std::unexpected(\"can't movetoroot in the current workspace\");\n    } else if (ARGS[0] == \"preselect\") {\n        auto direction = ARGS[1];\n\n        if (direction.empty()) {\n            Log::logger->log(Log::ERR, \"Expected direction for preselect\");\n            return std::unexpected(\"No direction for preselect\");\n        }\n\n        switch (direction.front()) {\n            case 'u':\n            case 't': {\n                m_overrideDirection = Math::DIRECTION_UP;\n                break;\n            }\n            case 'd':\n            case 'b': {\n                m_overrideDirection = Math::DIRECTION_DOWN;\n                break;\n            }\n            case 'r': {\n                m_overrideDirection = Math::DIRECTION_RIGHT;\n                break;\n            }\n            case 'l': {\n                m_overrideDirection = Math::DIRECTION_LEFT;\n                break;\n            }\n            default: {\n                // any other character resets the focus direction\n                // needed for the persistent mode\n                m_overrideDirection = Math::DIRECTION_DEFAULT;\n                break;\n            }\n        }\n    } else if (ARGS[0] == \"splitratio\") {\n        auto ratio = ARGS[1];\n        bool exact = ARGS[2].starts_with(\"exact\");\n\n        if (ratio.empty())\n            return std::unexpected(\"splitratio requires an arg\");\n\n        auto delta = getPlusMinusKeywordResult(std::string{ratio}, 0.F);\n\n        if (!CURRENT_NODE || !CURRENT_NODE->pParent)\n            return std::unexpected(\"cannot alter split ratio on no / single node\");\n\n        if (!delta)\n            return std::unexpected(std::format(\"failed to parse \\\"{}\\\" as a delta\", ratio));\n\n        const float newRatio              = exact ? *delta : CURRENT_NODE->pParent->splitRatio + *delta;\n        CURRENT_NODE->pParent->splitRatio = std::clamp(newRatio, 0.1F, 1.9F);\n\n        CURRENT_NODE->pParent->recalcSizePosRecursive();\n    }\n\n    return {};\n}\n\nbool CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) {\n    if (!x || !x->pParent)\n        return false;\n\n    if (x->pTarget->fullscreenMode() != FSMODE_NONE)\n        return false;\n\n    x->pParent->splitTop = !x->pParent->splitTop;\n\n    x->pParent->recalcSizePosRecursive();\n\n    return true;\n}\n\nbool CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) {\n    if (x->pTarget->fullscreenMode() != FSMODE_NONE || !x->pParent)\n        return false;\n\n    std::swap(x->pParent->children[0], x->pParent->children[1]);\n\n    x->pParent->recalcSizePosRecursive();\n\n    return true;\n}\n\nvoid CDwindleAlgorithm::rotateSplit(SP<SDwindleNodeData> x, int angle) {\n    if (!x || !x->pParent)\n        return;\n\n    if (x->pTarget->fullscreenMode() != FSMODE_NONE)\n        return;\n\n    // normalize the angle to multiples of 90 degrees\n    int  normalizedAngle = ((sc<int>(angle / 90) % 4) + 4) % 4; // ensures positive modulo\n\n    auto pParent = x->pParent;\n\n    bool shouldSwap = false;\n\n    switch (normalizedAngle) {\n        case 0: // 0 degrees - no change\n            break;\n        case 1:\n            if (pParent->splitTop)\n                shouldSwap = true;\n            pParent->splitTop = !pParent->splitTop;\n            break;\n        case 2: shouldSwap = true; break;\n        case 3:\n            if (!pParent->splitTop)\n                shouldSwap = true;\n            pParent->splitTop = !pParent->splitTop;\n            break;\n        default: break; // should never happen\n    }\n\n    if (shouldSwap)\n        std::swap(pParent->children[0], pParent->children[1]);\n\n    pParent->recalcSizePosRecursive();\n}\n\nbool CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {\n    if (!x || !x->pParent)\n        return false;\n\n    if (x->pTarget->fullscreenMode() != FSMODE_NONE)\n        return false;\n\n    // already at root\n    if (!x->pParent->pParent)\n        return false;\n\n    auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1];\n\n    // instead of [getMasterNodeOnWorkspace], we walk back to root since we need\n    // to know which children of root is our ancestor\n    auto pAncestor = x, pRoot = x->pParent.lock();\n    while (pRoot->pParent) {\n        pAncestor = pRoot;\n        pRoot     = pRoot->pParent.lock();\n    }\n\n    auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0];\n    std::swap(pNode, pSwap);\n    std::swap(pNode->pParent, pSwap->pParent);\n\n    // [stable] in that the focused window occupies same side of screen\n    if (stable)\n        std::swap(pRoot->children[0], pRoot->children[1]);\n\n    pRoot->recalcSizePosRecursive();\n\n    return true;\n}\n"
  },
  {
    "path": "src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp",
    "content": "#include \"../../TiledAlgorithm.hpp\"\n\nnamespace Layout {\n    class CAlgorithm;\n}\n\nnamespace Layout::Tiled {\n    struct SDwindleNodeData;\n\n    class CDwindleAlgorithm : public ITiledAlgorithm {\n      public:\n        CDwindleAlgorithm()          = default;\n        virtual ~CDwindleAlgorithm() = default;\n\n        virtual void                             newTarget(SP<ITarget> target);\n        virtual void                             movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual void                             removeTarget(SP<ITarget> target);\n\n        virtual void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        virtual void                             recalculate();\n\n        virtual SP<ITarget>                      getNextCandidate(SP<ITarget> old);\n\n        virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n        virtual std::optional<Vector2D>          predictSizeForNewTarget();\n\n        virtual void                             swapTargets(SP<ITarget> a, SP<ITarget> b);\n        virtual void                             moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n      private:\n        std::vector<SP<SDwindleNodeData>> m_dwindleNodesData;\n\n        struct {\n            bool started = false;\n            bool pseudo  = false;\n            bool xExtent = false;\n            bool yExtent = false;\n        } m_pseudoDragFlags;\n\n        std::optional<Vector2D> m_overrideFocalPoint; // for onWindowCreatedTiling.\n\n        void                    addTarget(SP<ITarget> target);\n        void                    calculateWorkspace();\n        SP<SDwindleNodeData>    getNodeFromTarget(SP<ITarget>);\n        SP<SDwindleNodeData>    getNodeFromWindow(PHLWINDOW w);\n        int                     getNodes();\n        SP<SDwindleNodeData>    getFirstNode();\n        SP<SDwindleNodeData>    getClosestNode(const Vector2D&, SP<ITarget> skip = nullptr);\n        SP<SDwindleNodeData>    getMasterNode();\n\n        bool                    toggleSplit(SP<SDwindleNodeData>);\n        bool                    swapSplit(SP<SDwindleNodeData>);\n        void                    rotateSplit(SP<SDwindleNodeData>, int angle = 90);\n        bool                    moveToRoot(SP<SDwindleNodeData>, bool stable = true);\n\n        Math::eDirection        m_overrideDirection = Math::DIRECTION_DEFAULT;\n    };\n};\n"
  },
  {
    "path": "src/layout/algorithm/tiled/master/MasterAlgorithm.cpp",
    "content": "#include \"MasterAlgorithm.hpp\"\n\n#include \"../../Algorithm.hpp\"\n#include \"../../../space/Space.hpp\"\n#include \"../../../target/WindowTarget.hpp\"\n\n#include \"../../../../config/ConfigValue.hpp\"\n#include \"../../../../config/ConfigManager.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../helpers/Monitor.hpp\"\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Layout;\nusing namespace Layout::Tiled;\n\nstruct Layout::Tiled::SMasterNodeData {\n    bool        isMaster   = false;\n    float       percMaster = 0.5f;\n\n    WP<ITarget> pTarget;\n\n    Vector2D    position;\n    Vector2D    size;\n\n    float       percSize = 1.f; // size multiplier for resizing children\n\n    bool        ignoreFullscreenChecks = false;\n\n    //\n    bool operator==(const SMasterNodeData& rhs) const {\n        return pTarget.lock() == rhs.pTarget.lock();\n    }\n};\n\nvoid CMasterAlgorithm::newTarget(SP<ITarget> target) {\n    addTarget(target, true);\n}\n\nvoid CMasterAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {\n    addTarget(target, false);\n}\n\nvoid CMasterAlgorithm::addTarget(SP<ITarget> target, bool firstMap) {\n    static auto PNEWONACTIVE = CConfigValue<std::string>(\"master:new_on_active\");\n    static auto PNEWONTOP    = CConfigValue<Hyprlang::INT>(\"master:new_on_top\");\n    static auto PNEWSTATUS   = CConfigValue<std::string>(\"master:new_status\");\n\n    const auto  PWORKSPACE = m_parent->space()->workspace();\n    const auto  PMONITOR   = PWORKSPACE->m_monitor;\n\n    bool        dragOntoMaster = false;\n\n    if (g_layoutManager->dragController()->wasDraggingWindow()) {\n        if (const auto n = getClosestNode(g_pInputManager->getMouseCoordsInternal()); n && n->isMaster)\n            dragOntoMaster = true;\n    }\n\n    const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == \"before\";\n    const bool BNEWISMASTER     = dragOntoMaster || *PNEWSTATUS == \"master\";\n\n    const auto PNODE = [&]() -> SP<SMasterNodeData> {\n        if (*PNEWONACTIVE != \"none\" && !BNEWISMASTER) {\n            const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window());\n            if (pLastNode && !(pLastNode->isMaster && (getMastersNo() == 1 || *PNEWSTATUS == \"slave\"))) {\n                auto it = std::ranges::find(m_masterNodesData, pLastNode);\n                if (!BNEWBEFOREACTIVE)\n                    ++it;\n                return *m_masterNodesData.emplace(it, makeShared<SMasterNodeData>());\n            }\n        }\n        return *PNEWONTOP ? *m_masterNodesData.emplace(m_masterNodesData.begin(), makeShared<SMasterNodeData>()) : m_masterNodesData.emplace_back(makeShared<SMasterNodeData>());\n    }();\n\n    PNODE->pTarget = target;\n\n    const auto   WINDOWSONWORKSPACE = getNodesNo();\n    static auto  PMFACT             = CConfigValue<Hyprlang::FLOAT>(\"master:mfact\");\n    float        lastSplitPercent   = *PMFACT;\n\n    auto         OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == PWORKSPACE ?\n        getNodeFromWindow(Desktop::focusState()->window()) :\n        getMasterNode();\n\n    const auto   MOUSECOORDS   = g_pInputManager->getMouseCoordsInternal();\n    static auto  PDROPATCURSOR = CConfigValue<Hyprlang::INT>(\"master:drop_at_cursor\");\n    eOrientation orientation   = getDynamicOrientation();\n    const auto   NODEIT        = std::ranges::find(m_masterNodesData, PNODE);\n\n    bool         forceDropAsMaster = false;\n    // if dragging window to move, drop it at the cursor position instead of bottom/top of stack\n    if (*PDROPATCURSOR && g_layoutManager->dragController()->mode() == MBIND_MOVE) {\n        if (WINDOWSONWORKSPACE > 2) {\n            auto&             v = m_masterNodesData;\n\n            const std::size_t srcIndex = static_cast<std::size_t>(std::distance(v.begin(), NODEIT));\n\n            for (std::size_t i = 0; i < v.size(); ++i) {\n                const CBox box = v[i]->pTarget->position();\n                if (!box.containsPoint(MOUSECOORDS))\n                    continue;\n\n                std::size_t insertIndex = i;\n\n                switch (orientation) {\n                    case ORIENTATION_LEFT:\n                    case ORIENTATION_RIGHT:\n                        if (MOUSECOORDS.y > box.middle().y)\n                            ++insertIndex; // insert after\n                        break;\n\n                    case ORIENTATION_TOP:\n                    case ORIENTATION_BOTTOM:\n                        if (MOUSECOORDS.x > box.middle().x)\n                            ++insertIndex; // insert after\n                        break;\n\n                    case ORIENTATION_CENTER: break;\n\n                    default: UNREACHABLE();\n                }\n\n                if (insertIndex > srcIndex)\n                    --insertIndex;\n\n                if (insertIndex == srcIndex)\n                    break;\n\n                auto node = std::move(v[srcIndex]);\n                v.erase(v.begin() + static_cast<std::ptrdiff_t>(srcIndex));\n                v.insert(v.begin() + static_cast<std::ptrdiff_t>(insertIndex), std::move(node));\n\n                break;\n            }\n        } else if (WINDOWSONWORKSPACE == 2) {\n            // when dropping as the second tiled window in the workspace,\n            // make it the master only if the cursor is on the master side of the screen\n            for (auto const& nd : m_masterNodesData) {\n                if (nd->isMaster) {\n                    const auto MIDDLE = nd->pTarget->position().middle();\n                    switch (orientation) {\n                        case ORIENTATION_LEFT:\n                        case ORIENTATION_CENTER:\n                            if (MOUSECOORDS.x < MIDDLE.x)\n                                forceDropAsMaster = true;\n                            break;\n                        case ORIENTATION_RIGHT:\n                            if (MOUSECOORDS.x > MIDDLE.x)\n                                forceDropAsMaster = true;\n                            break;\n                        case ORIENTATION_TOP:\n                            if (MOUSECOORDS.y < MIDDLE.y)\n                                forceDropAsMaster = true;\n                            break;\n                        case ORIENTATION_BOTTOM:\n                            if (MOUSECOORDS.y > MIDDLE.y)\n                                forceDropAsMaster = true;\n                            break;\n                        default: UNREACHABLE();\n                    }\n                    break;\n                }\n            }\n        }\n    }\n\n    if (BNEWISMASTER                                                                 //\n        || WINDOWSONWORKSPACE == 1                                                   //\n        || (WINDOWSONWORKSPACE > 2 && !firstMap && OPENINGON && OPENINGON->isMaster) //\n        || forceDropAsMaster                                                         //\n        || (*PNEWSTATUS == \"inherit\" && OPENINGON && OPENINGON->isMaster && g_layoutManager->dragController()->mode() != MBIND_MOVE)) {\n\n        if (BNEWBEFOREACTIVE) {\n            for (auto& nd : m_masterNodesData | std::views::reverse) {\n                if (nd->isMaster) {\n                    nd->isMaster     = false;\n                    lastSplitPercent = nd->percMaster;\n                    break;\n                }\n            }\n        } else {\n            for (auto& nd : m_masterNodesData) {\n                if (nd->isMaster) {\n                    nd->isMaster     = false;\n                    lastSplitPercent = nd->percMaster;\n                    break;\n                }\n            }\n        }\n\n        PNODE->isMaster   = true;\n        PNODE->percMaster = lastSplitPercent;\n\n        // first, check if it isn't too big.\n        if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PMONITOR->m_size.x * lastSplitPercent || MAXSIZE.y < PMONITOR->m_size.y) {\n            // we can't continue. make it floating.\n            m_parent->setFloating(target, true, true);\n            std::erase(m_masterNodesData, PNODE);\n            return;\n        }\n    } else {\n        PNODE->isMaster   = false;\n        PNODE->percMaster = lastSplitPercent;\n\n        // first, check if it isn't too big.\n        if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX);\n            MAXSIZE.x < PMONITOR->m_size.x * (1 - lastSplitPercent) || MAXSIZE.y < PMONITOR->m_size.y * (1.f / (WINDOWSONWORKSPACE - 1))) {\n            // we can't continue. make it floating.\n            m_parent->setFloating(target, true);\n            std::erase(m_masterNodesData, PNODE);\n            return;\n        }\n    }\n\n    // recalc\n    calculateWorkspace();\n}\n\nvoid CMasterAlgorithm::removeTarget(SP<ITarget> target) {\n    const auto  MASTERSLEFT = getMastersNo();\n    static auto SMALLSPLIT  = CConfigValue<Hyprlang::INT>(\"master:allow_small_split\");\n\n    const auto  PNODE = getNodeFromTarget(target);\n\n    if (target->fullscreenMode() != FSMODE_NONE)\n        g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE);\n\n    if (PNODE->isMaster && (MASTERSLEFT <= 1 || *SMALLSPLIT == 1)) {\n        // find a new master from top of the list\n        for (auto& nd : m_masterNodesData) {\n            if (!nd->isMaster) {\n                nd->isMaster   = true;\n                nd->percMaster = PNODE->percMaster;\n                break;\n            }\n        }\n    }\n\n    std::erase(m_masterNodesData, PNODE);\n\n    if (getMastersNo() == getNodesNo() && MASTERSLEFT > 1) {\n        for (auto& nd : m_masterNodesData | std::views::reverse) {\n            nd->isMaster = false;\n            break;\n        }\n    }\n    // BUGFIX: correct bug where closing one master in a stack of 2 would leave\n    // the screen half bare, and make it difficult to select remaining window\n    if (getNodesNo() == 1) {\n        for (auto& nd : m_masterNodesData) {\n            if (!nd->isMaster) {\n                nd->isMaster = true;\n                break;\n            }\n        }\n    }\n\n    calculateWorkspace();\n}\n\nvoid CMasterAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    const auto PNODE = getNodeFromTarget(target);\n\n    if (!PNODE)\n        return;\n\n    const auto   PMONITOR            = m_parent->space()->workspace()->m_monitor;\n    static auto  SLAVECOUNTFORCENTER = CConfigValue<Hyprlang::INT>(\"master:slave_count_for_center_master\");\n    static auto  PSMARTRESIZING      = CConfigValue<Hyprlang::INT>(\"master:smart_resizing\");\n\n    const auto   WORKAREA      = PMONITOR->logicalBoxMinusReserved();\n    const bool   DISPLAYBOTTOM = STICKS(PNODE->position.y + PNODE->size.y, WORKAREA.y + WORKAREA.h);\n    const bool   DISPLAYRIGHT  = STICKS(PNODE->position.x + PNODE->size.x, WORKAREA.x + WORKAREA.w);\n    const bool   DISPLAYTOP    = STICKS(PNODE->position.y, WORKAREA.y);\n    const bool   DISPLAYLEFT   = STICKS(PNODE->position.x, WORKAREA.x);\n\n    const bool   LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT;\n    const bool   TOP  = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT;\n    const bool   NONE = corner == CORNER_NONE;\n\n    const auto   MASTERS      = getMastersNo();\n    const auto   WINDOWS      = getNodesNo();\n    const auto   STACKWINDOWS = WINDOWS - MASTERS;\n\n    eOrientation orientation = getDynamicOrientation();\n    bool         centered    = orientation == ORIENTATION_CENTER && (STACKWINDOWS >= *SLAVECOUNTFORCENTER);\n    double       delta       = 0;\n\n    if (getNodesNo() == 1 && !centered)\n        return;\n\n    m_forceWarps = true;\n\n    switch (orientation) {\n        case ORIENTATION_LEFT: delta = Δ.x / PMONITOR->m_size.x; break;\n        case ORIENTATION_RIGHT: delta = -Δ.x / PMONITOR->m_size.x; break;\n        case ORIENTATION_BOTTOM: delta = -Δ.y / PMONITOR->m_size.y; break;\n        case ORIENTATION_TOP: delta = Δ.y / PMONITOR->m_size.y; break;\n        case ORIENTATION_CENTER:\n            delta = Δ.x / PMONITOR->m_size.x;\n            if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) {\n                if (!NONE || !PNODE->isMaster)\n                    delta *= 2;\n                if ((!PNODE->isMaster && DISPLAYLEFT) || (PNODE->isMaster && LEFT && *PSMARTRESIZING))\n                    delta = -delta;\n            }\n            break;\n        default: UNREACHABLE();\n    }\n\n    for (auto& n : m_masterNodesData) {\n        if (n->isMaster)\n            n->percMaster = std::clamp(n->percMaster + delta, 0.05, 0.95);\n    }\n\n    // check the up/down resize\n    const bool isStackVertical = orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT || orientation == ORIENTATION_CENTER;\n\n    const auto RESIZEDELTA = isStackVertical ? Δ.y : Δ.x;\n\n    auto       nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS;\n    if (orientation == ORIENTATION_CENTER && !PNODE->isMaster)\n        nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2;\n\n    const auto SIZE = isStackVertical ? WORKAREA.h / nodesInSameColumn : WORKAREA.w / nodesInSameColumn;\n\n    if (RESIZEDELTA != 0 && nodesInSameColumn > 1) {\n        if (!*PSMARTRESIZING) {\n            PNODE->percSize = std::clamp(PNODE->percSize + RESIZEDELTA / SIZE, 0.05, 1.95);\n        } else {\n            const auto  NODEIT    = std::ranges::find(m_masterNodesData, PNODE);\n            const auto  REVNODEIT = std::ranges::find(m_masterNodesData | std::views::reverse, PNODE);\n\n            const float totalSize       = isStackVertical ? WORKAREA.h : WORKAREA.w;\n            const float minSize         = totalSize / nodesInSameColumn * 0.2;\n            const bool  resizePrevNodes = isStackVertical ? (TOP || DISPLAYBOTTOM) && !DISPLAYTOP : (LEFT || DISPLAYRIGHT) && !DISPLAYLEFT;\n\n            int         nodesLeft = 0;\n            float       sizeLeft  = 0;\n            int         nodeCount = 0;\n            // check the sizes of all the nodes to be resized for later calculation\n            auto checkNodesLeft = [&sizeLeft, &nodesLeft, orientation, isStackVertical, &nodeCount, PNODE](auto it) {\n                if (it->isMaster != PNODE->isMaster)\n                    return;\n                nodeCount++;\n                if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1)\n                    return;\n                sizeLeft += isStackVertical ? it->size.y : it->size.x;\n                nodesLeft++;\n            };\n            float resizeDiff;\n            if (resizePrevNodes) {\n                std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), checkNodesLeft);\n                resizeDiff = -RESIZEDELTA;\n            } else {\n                std::for_each(std::next(NODEIT), m_masterNodesData.end(), checkNodesLeft);\n                resizeDiff = RESIZEDELTA;\n            }\n\n            const float nodeSize        = isStackVertical ? PNODE->size.y : PNODE->size.x;\n            const float maxSizeIncrease = sizeLeft - nodesLeft * minSize;\n            const float maxSizeDecrease = minSize - nodeSize;\n\n            // leaves enough room for the other nodes\n            resizeDiff = std::clamp(resizeDiff, maxSizeDecrease, maxSizeIncrease);\n            PNODE->percSize += resizeDiff / SIZE;\n\n            // resize the other nodes\n            nodeCount            = 0;\n            auto resizeNodesLeft = [maxSizeIncrease, resizeDiff, minSize, orientation, isStackVertical, SIZE, &nodeCount, nodesLeft, PNODE](auto& it) {\n                if (it->isMaster != PNODE->isMaster)\n                    return;\n                nodeCount++;\n                // if center orientation, only resize when on the same side\n                if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1)\n                    return;\n                const float size               = isStackVertical ? it->size.y : it->size.x;\n                const float resizeDeltaForEach = maxSizeIncrease != 0 ? resizeDiff * (size - minSize) / maxSizeIncrease : resizeDiff / nodesLeft;\n                it->percSize -= resizeDeltaForEach / SIZE;\n            };\n            if (resizePrevNodes)\n                std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), resizeNodesLeft);\n            else\n                std::for_each(std::next(NODEIT), m_masterNodesData.end(), resizeNodesLeft);\n        }\n    }\n\n    recalculate();\n\n    m_forceWarps = false;\n}\n\nvoid CMasterAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {\n    auto nodeA = getNodeFromTarget(a);\n    auto nodeB = getNodeFromTarget(b);\n\n    if (nodeA)\n        nodeA->pTarget = b;\n    if (nodeB)\n        nodeB->pTarget = a;\n}\n\nvoid CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>(\"binds:window_direction_monitor_fallback\");\n\n    const auto  PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir);\n\n    if (!t->window())\n        return;\n\n    PHLWORKSPACE targetWs;\n\n    if (!PWINDOW2 && t->space() && t->space()->workspace()) {\n        // try to find a monitor in dir\n        const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir);\n        if (PMONINDIR)\n            targetWs = PMONINDIR->m_activeWorkspace;\n    } else\n        targetWs = PWINDOW2->m_workspace;\n\n    if (!targetWs)\n        return;\n\n    t->window()->setAnimationsToMove();\n\n    if (t->window()->m_workspace != targetWs) {\n        if (!*PMONITORFALLBACK)\n            return; // noop\n\n        t->assignToSpace(targetWs->m_space, focalPointForDir(t, dir));\n    } else if (PWINDOW2) {\n        // if same monitor, switch windows\n        g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget());\n        if (silent)\n            Desktop::focusState()->fullWindowFocus(PWINDOW2, Desktop::FOCUS_REASON_KEYBIND);\n\n        recalculate();\n    }\n}\n\nvoid CMasterAlgorithm::recalculate() {\n    calculateWorkspace();\n}\n\nstd::expected<void, std::string> CMasterAlgorithm::layoutMsg(const std::string_view& sv) {\n    auto switchToWindow = [&](SP<ITarget> target) {\n        if (!target || !validMapped(target->window()))\n            return;\n\n        Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_KEYBIND);\n        g_pCompositor->warpCursorTo(target->position().middle());\n\n        g_pInputManager->m_forcedFocus = target->window();\n        g_pInputManager->simulateMouseMovement();\n        g_pInputManager->m_forcedFocus.reset();\n    };\n\n    CVarList2 vars(std::string{sv}, 0, 's');\n\n    if (vars.size() < 1 || vars[0].empty()) {\n        Log::logger->log(Log::ERR, \"layoutmsg called without params\");\n        return std::unexpected(\"layoutmsg without params\");\n    }\n\n    auto command = vars[0];\n\n    // swapwithmaster <master | child | auto> <ignoremaster>\n    // first message argument can have the following values:\n    // * master - keep the focus at the new master\n    // * child - keep the focus at the new child\n    // * auto (default) - swap the focus (keep the focus of the previously selected window)\n    // * ignoremaster - ignore if master is focused\n\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (command == \"swapwithmaster\") {\n        if (!PWINDOW)\n            return std::unexpected(\"No focused window\");\n\n        if (!isWindowTiled(PWINDOW))\n            return std::unexpected(\"focused window isn't tiled\");\n\n        const auto PMASTER = getMasterNode();\n\n        if (!PMASTER)\n            return std::unexpected(\"no master node\");\n\n        const auto NEWCHILD = PMASTER->pTarget.lock();\n\n        const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == \"ignoremaster\"; });\n\n        if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) {\n            const auto& NEWMASTER       = PWINDOW->layoutTarget();\n            const bool  newFocusToChild = vars.size() >= 2 && vars[1] == \"child\";\n            g_layoutManager->switchTargets(NEWMASTER, NEWCHILD);\n            const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER;\n            switchToWindow(NEWFOCUS);\n        } else if (!IGNORE_IF_MASTER) {\n            for (auto const& n : m_masterNodesData) {\n                if (!n->isMaster) {\n                    const auto NEWMASTER = n->pTarget.lock();\n                    g_layoutManager->switchTargets(NEWMASTER, NEWCHILD);\n                    const bool newFocusToMaster = vars.size() >= 2 && vars[1] == \"master\";\n                    const auto NEWFOCUS         = newFocusToMaster ? NEWMASTER : NEWCHILD;\n                    switchToWindow(NEWFOCUS);\n                    break;\n                }\n            }\n        }\n\n        return {};\n    }\n    // focusmaster <master | previous | auto>\n    // first message argument can have the following values:\n    // * master - keep the focus at the new master, even if it was focused before\n    // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto`\n    // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master\n    else if (command == \"focusmaster\") {\n        if (!PWINDOW)\n            return std::unexpected(\"no focused window\");\n\n        const auto PMASTER = getMasterNode();\n\n        if (!PMASTER)\n            return std::unexpected(\"no master\");\n\n        const auto& ARG = vars[1]; // returns empty string if out of bounds\n\n        if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) {\n            switchToWindow(PMASTER->pTarget.lock());\n            // save previously focused window (only for `previous` mode)\n            if (ARG == \"previous\")\n                m_workspaceData.focusMasterPrev = PWINDOW->layoutTarget();\n            return {};\n        }\n\n        const auto focusAuto = [&]() {\n            // focus first non-master window\n            for (auto const& n : m_masterNodesData) {\n                if (!n->isMaster) {\n                    switchToWindow(n->pTarget.lock());\n                    break;\n                }\n            }\n        };\n\n        if (ARG == \"master\")\n            return {};\n        // switch to previously saved window\n        else if (ARG == \"previous\") {\n            const auto PREVWINDOW = m_workspaceData.focusMasterPrev.lock();\n            const bool VALID      = PREVWINDOW && getNodeFromWindow(PREVWINDOW->window()) && (PWINDOW != PREVWINDOW->window());\n            VALID ? switchToWindow(PREVWINDOW) : focusAuto();\n        } else\n            focusAuto();\n    } else if (command == \"cyclenext\") {\n        if (!PWINDOW)\n            return std::unexpected(\"no window\");\n\n        const bool NOLOOP      = vars.size() >= 2 && vars[1] == \"noloop\";\n        const auto PNEXTWINDOW = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP);\n        switchToWindow(PNEXTWINDOW);\n    } else if (command == \"cycleprev\") {\n        if (!PWINDOW)\n            return std::unexpected(\"no window\");\n\n        const bool NOLOOP      = vars.size() >= 2 && vars[1] == \"noloop\";\n        const auto PPREVWINDOW = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP);\n        switchToWindow(PPREVWINDOW);\n    } else if (command == \"swapnext\") {\n        if (!validMapped(PWINDOW))\n            return std::unexpected(\"no window\");\n\n        if (PWINDOW->layoutTarget()->floating()) {\n            g_pKeybindManager->m_dispatchers[\"swapnext\"](\"\");\n            return {};\n        }\n\n        const bool NOLOOP            = vars.size() >= 2 && vars[1] == \"noloop\";\n        const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP);\n\n        if (PWINDOWTOSWAPWITH) {\n            g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n            g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH);\n            switchToWindow(PWINDOW->layoutTarget());\n        }\n    } else if (command == \"swapprev\") {\n        if (!validMapped(PWINDOW))\n            return std::unexpected(\"no window\");\n\n        if (PWINDOW->layoutTarget()->floating()) {\n            g_pKeybindManager->m_dispatchers[\"swapnext\"](\"prev\");\n            return {};\n        }\n\n        const bool NOLOOP            = vars.size() >= 2 && vars[1] == \"noloop\";\n        const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP);\n\n        if (PWINDOWTOSWAPWITH) {\n            g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n            g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH);\n            switchToWindow(PWINDOW->layoutTarget());\n        }\n    } else if (command == \"addmaster\") {\n        if (!validMapped(PWINDOW))\n            return std::unexpected(\"no window\");\n\n        if (PWINDOW->layoutTarget()->floating())\n            return std::unexpected(\"window is floating\");\n\n        const auto  PNODE = getNodeFromTarget(PWINDOW->layoutTarget());\n\n        const auto  WINDOWS    = getNodesNo();\n        const auto  MASTERS    = getMastersNo();\n        static auto SMALLSPLIT = CConfigValue<Hyprlang::INT>(\"master:allow_small_split\");\n\n        if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0)\n            return std::unexpected(\"nothing to do\");\n\n        g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n\n        if (!PNODE || PNODE->isMaster) {\n            // first non-master node\n            for (auto& n : m_masterNodesData) {\n                if (!n->isMaster) {\n                    n->isMaster = true;\n                    break;\n                }\n            }\n        } else {\n            PNODE->isMaster = true;\n        }\n\n        calculateWorkspace();\n\n    } else if (command == \"removemaster\") {\n\n        if (!validMapped(PWINDOW))\n            return std::unexpected(\"no window\");\n\n        if (PWINDOW->layoutTarget()->floating())\n            return std::unexpected(\"window isnt tiled\");\n\n        const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget());\n\n        const auto WINDOWS = getNodesNo();\n        const auto MASTERS = getMastersNo();\n\n        if (WINDOWS < 2 || MASTERS < 2)\n            return std::unexpected(\"nothing to do\");\n\n        g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n\n        if (!PNODE || !PNODE->isMaster) {\n            // first non-master node\n            for (auto& nd : m_masterNodesData | std::views::reverse) {\n                if (nd->isMaster) {\n                    nd->isMaster = false;\n                    break;\n                }\n            }\n        } else {\n            PNODE->isMaster = false;\n        }\n\n        calculateWorkspace();\n    } else if (command == \"orientationleft\" || command == \"orientationright\" || command == \"orientationtop\" || command == \"orientationbottom\" || command == \"orientationcenter\") {\n        if (!PWINDOW)\n            return std::unexpected(\"no window\");\n\n        g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n\n        if (command == \"orientationleft\")\n            m_workspaceData.explicitOrientation = ORIENTATION_LEFT;\n        else if (command == \"orientationright\")\n            m_workspaceData.explicitOrientation = ORIENTATION_RIGHT;\n        else if (command == \"orientationtop\")\n            m_workspaceData.explicitOrientation = ORIENTATION_TOP;\n        else if (command == \"orientationbottom\")\n            m_workspaceData.explicitOrientation = ORIENTATION_BOTTOM;\n        else if (command == \"orientationcenter\")\n            m_workspaceData.explicitOrientation = ORIENTATION_CENTER;\n\n        calculateWorkspace();\n    } else if (command == \"orientationnext\") {\n        runOrientationCycle(nullptr, 1);\n    } else if (command == \"orientationprev\") {\n        runOrientationCycle(nullptr, -1);\n    } else if (command == \"orientationcycle\") {\n        runOrientationCycle(&vars, 1);\n    } else if (command == \"mfact\") {\n\n        if (!PWINDOW)\n            return std::unexpected(\"no window\");\n\n        const bool exact = vars[1] == \"exact\";\n\n        float      ratio = 0.F;\n\n        try {\n            ratio = std::stof(std::string{exact ? vars[2] : vars[1]});\n        } catch (...) { return std::unexpected(\"bad ratio\"); }\n\n        const auto PNODE = getNodeFromWindow(PWINDOW);\n\n        const auto PMASTER = getMasterNode();\n\n        float      newRatio = exact ? ratio : PMASTER->percMaster + ratio;\n        PMASTER->percMaster = std::clamp(newRatio, 0.05f, 0.95f);\n\n        recalculate();\n    } else if (command == \"rollnext\") {\n        const auto PNODE = getNodeFromWindow(PWINDOW);\n\n        if (!PNODE)\n            return std::unexpected(\"window couldnt be found\");\n\n        const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode();\n        if (!OLDMASTER)\n            return std::unexpected(\"no old master\");\n\n        auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);\n\n        for (auto& nd : m_masterNodesData) {\n            if (!nd->isMaster) {\n                const auto& newMaster = nd;\n                newMaster->isMaster   = true;\n\n                auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);\n\n                if (newMasterIt < oldMasterIt)\n                    std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt);\n                else if (newMasterIt > oldMasterIt)\n                    std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt));\n\n                switchToWindow(newMaster->pTarget.lock());\n                OLDMASTER->isMaster = false;\n\n                oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);\n                if (oldMasterIt != m_masterNodesData.end())\n                    std::ranges::rotate(oldMasterIt, std::next(oldMasterIt), m_masterNodesData.end());\n\n                break;\n            }\n        }\n\n        calculateWorkspace();\n    } else if (command == \"rollprev\") {\n        const auto PNODE = getNodeFromWindow(PWINDOW);\n\n        if (!PNODE)\n            return std::unexpected(\"window couldnt be found\");\n\n        const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode();\n        if (!OLDMASTER)\n            return std::unexpected(\"no old master\");\n\n        auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);\n\n        for (auto& nd : m_masterNodesData | std::views::reverse) {\n            if (!nd->isMaster) {\n                const auto& newMaster = nd;\n                newMaster->isMaster   = true;\n\n                auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);\n\n                if (newMasterIt < oldMasterIt)\n                    std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt);\n                else if (newMasterIt > oldMasterIt)\n                    std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt));\n\n                switchToWindow(newMaster->pTarget.lock());\n                OLDMASTER->isMaster = false;\n\n                oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);\n                if (oldMasterIt != m_masterNodesData.begin())\n                    std::ranges::rotate(m_masterNodesData.begin(), oldMasterIt, std::next(oldMasterIt));\n\n                break;\n            }\n        }\n\n        calculateWorkspace();\n    }\n\n    return {};\n}\n\nstd::optional<Vector2D> CMasterAlgorithm::predictSizeForNewTarget() {\n    static auto PNEWSTATUS = CConfigValue<std::string>(\"master:new_status\");\n\n    const auto  MONITOR = m_parent->space()->workspace()->m_monitor;\n\n    if (!MONITOR)\n        return std::nullopt;\n\n    const int NODES = getNodesNo();\n\n    if (NODES <= 0)\n        return Desktop::focusState()->monitor()->m_size;\n\n    const auto MASTER = getMasterNode();\n    if (!MASTER) // wtf\n        return std::nullopt;\n\n    if (*PNEWSTATUS == \"master\") {\n        return MASTER->size;\n    } else {\n        const auto SLAVES = NODES - getMastersNo();\n\n        // TODO: make this better\n        if (SLAVES == 0)\n            return Vector2D{MONITOR->m_size.x / 2.F, MONITOR->m_size.y};\n        else\n            return Vector2D{MONITOR->m_size.x - MASTER->size.x, MONITOR->m_size.y / (SLAVES + 1)};\n    }\n\n    return std::nullopt;\n}\n\nvoid CMasterAlgorithm::buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, Hyprutils::String::CVarList2* vars) {\n    for (size_t i = 1; i < vars->size(); ++i) {\n        if ((*vars)[i] == \"top\") {\n            cycle.emplace_back(ORIENTATION_TOP);\n        } else if ((*vars)[i] == \"right\") {\n            cycle.emplace_back(ORIENTATION_RIGHT);\n        } else if ((*vars)[i] == \"bottom\") {\n            cycle.emplace_back(ORIENTATION_BOTTOM);\n        } else if ((*vars)[i] == \"left\") {\n            cycle.emplace_back(ORIENTATION_LEFT);\n        } else if ((*vars)[i] == \"center\") {\n            cycle.emplace_back(ORIENTATION_CENTER);\n        }\n    }\n}\n\nvoid CMasterAlgorithm::buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle) {\n    for (int i = 0; i <= ORIENTATION_CENTER; ++i) {\n        cycle.push_back(sc<eOrientation>(i));\n    }\n}\n\neOrientation CMasterAlgorithm::defaultOrientation() {\n    static auto PORIENT = CConfigValue<std::string>(\"master:orientation\");\n\n    const auto  WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace());\n    std::string orientationString;\n    if (WORKSPACERULE.layoutopts.contains(\"orientation\"))\n        orientationString = WORKSPACERULE.layoutopts.at(\"orientation\");\n    else\n        orientationString = *PORIENT;\n\n    eOrientation orientation = ORIENTATION_LEFT;\n    // override if workspace rule is set\n    if (!orientationString.empty()) {\n        if (orientationString == \"top\")\n            orientation = ORIENTATION_TOP;\n        else if (orientationString == \"right\")\n            orientation = ORIENTATION_RIGHT;\n        else if (orientationString == \"bottom\")\n            orientation = ORIENTATION_BOTTOM;\n        else if (orientationString == \"center\")\n            orientation = ORIENTATION_CENTER;\n        else\n            orientation = ORIENTATION_LEFT;\n    }\n\n    return orientation;\n}\n\nvoid CMasterAlgorithm::runOrientationCycle(Hyprutils::String::CVarList2* vars, int next) {\n    std::vector<eOrientation> cycle;\n    if (vars != nullptr)\n        buildOrientationCycleVectorFromVars(cycle, vars);\n\n    if (cycle.empty())\n        buildOrientationCycleVectorFromEOperation(cycle);\n\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return;\n\n    g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n\n    int nextOrPrev = 0;\n    for (size_t i = 0; i < cycle.size(); ++i) {\n        if (m_workspaceData.explicitOrientation.value_or(defaultOrientation()) == cycle[i]) {\n            nextOrPrev = i + next;\n            break;\n        }\n    }\n\n    if (nextOrPrev >= sc<int>(cycle.size()))\n        nextOrPrev = nextOrPrev % sc<int>(cycle.size());\n    else if (nextOrPrev < 0)\n        nextOrPrev = cycle.size() + (nextOrPrev % sc<int>(cycle.size()));\n\n    m_workspaceData.explicitOrientation = cycle.at(nextOrPrev);\n    calculateWorkspace();\n}\n\neOrientation CMasterAlgorithm::getDynamicOrientation() {\n    return m_workspaceData.explicitOrientation.value_or(defaultOrientation());\n}\n\nint CMasterAlgorithm::getNodesNo() {\n    return m_masterNodesData.size();\n}\n\nSP<SMasterNodeData> CMasterAlgorithm::getNodeFromWindow(PHLWINDOW x) {\n    return x ? getNodeFromTarget(x->layoutTarget()) : nullptr;\n}\n\nSP<SMasterNodeData> CMasterAlgorithm::getNodeFromTarget(SP<ITarget> x) {\n    for (const auto& n : m_masterNodesData) {\n        if (n->pTarget == x)\n            return n;\n    }\n\n    return nullptr;\n}\n\nSP<SMasterNodeData> CMasterAlgorithm::getMasterNode() {\n    for (const auto& n : m_masterNodesData) {\n        if (n->isMaster)\n            return n;\n    }\n\n    return nullptr;\n}\n\nvoid CMasterAlgorithm::calculateWorkspace() {\n    const auto PMASTERNODE = getMasterNode();\n\n    if (!PMASTERNODE)\n        return;\n\n    Hyprutils::Utils::CScopeGuard x([this] {\n        g_pHyprRenderer->damageMonitor(m_parent->space()->workspace()->m_monitor.lock());\n\n        if (!m_forceWarps)\n            return;\n\n        for (const auto& n : m_masterNodesData) {\n            n->pTarget->warpPositionSize();\n        }\n    });\n\n    eOrientation                  orientation         = getDynamicOrientation();\n    bool                          centerMasterWindow  = false;\n    static auto                   SLAVECOUNTFORCENTER = CConfigValue<Hyprlang::INT>(\"master:slave_count_for_center_master\");\n    static auto                   CMFALLBACK          = CConfigValue<std::string>(\"master:center_master_fallback\");\n    static auto                   PIGNORERESERVED     = CConfigValue<Hyprlang::INT>(\"master:center_ignores_reserved\");\n    static auto                   PSMARTRESIZING      = CConfigValue<Hyprlang::INT>(\"master:smart_resizing\");\n\n    const auto                    MASTERS          = getMastersNo();\n    const auto                    WINDOWS          = getNodesNo();\n    const auto                    STACKWINDOWS     = WINDOWS - MASTERS;\n    const auto                    WORKAREA         = m_parent->space()->workArea();\n    const auto                    PMONITOR         = m_parent->space()->workspace()->m_monitor;\n    const auto                    reservedLeft     = PMONITOR ? PMONITOR->m_reservedArea.left() : 0;\n    const auto                    reservedRight    = PMONITOR ? PMONITOR->m_reservedArea.right() : 0;\n    const auto                    UNRESERVED_WIDTH = WORKAREA.width + reservedLeft + reservedRight;\n\n    if (orientation == ORIENTATION_CENTER) {\n        if (STACKWINDOWS >= *SLAVECOUNTFORCENTER)\n            centerMasterWindow = true;\n        else {\n            if (*CMFALLBACK == \"left\")\n                orientation = ORIENTATION_LEFT;\n            else if (*CMFALLBACK == \"right\")\n                orientation = ORIENTATION_RIGHT;\n            else if (*CMFALLBACK == \"top\")\n                orientation = ORIENTATION_TOP;\n            else if (*CMFALLBACK == \"bottom\")\n                orientation = ORIENTATION_BOTTOM;\n            else\n                orientation = ORIENTATION_LEFT;\n        }\n    }\n\n    const float totalSize             = (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) ? WORKAREA.w : WORKAREA.h;\n    const float masterAverageSize     = totalSize / MASTERS;\n    const float slaveAverageSize      = totalSize / STACKWINDOWS;\n    float       masterAccumulatedSize = 0;\n    float       slaveAccumulatedSize  = 0;\n\n    if (*PSMARTRESIZING) {\n        // check the total width and height so that later\n        // if larger/smaller than screen size them down/up\n        for (auto const& nd : m_masterNodesData) {\n            if (nd->isMaster)\n                masterAccumulatedSize += totalSize / MASTERS * nd->percSize;\n            else\n                slaveAccumulatedSize += totalSize / STACKWINDOWS * nd->percSize;\n        }\n    }\n\n    // compute placement of master window(s)\n    if (WINDOWS == 1 && !centerMasterWindow) {\n        static auto PALWAYSKEEPPOSITION = CConfigValue<Hyprlang::INT>(\"master:always_keep_position\");\n        if (*PALWAYSKEEPPOSITION) {\n            const float WIDTH = WORKAREA.w * PMASTERNODE->percMaster;\n            float       nextX = 0;\n\n            if (orientation == ORIENTATION_RIGHT)\n                nextX = WORKAREA.w - WIDTH;\n            else if (orientation == ORIENTATION_CENTER)\n                nextX = (WORKAREA.w - WIDTH) / 2;\n\n            PMASTERNODE->size     = Vector2D(WIDTH, WORKAREA.h);\n            PMASTERNODE->position = WORKAREA.pos() + Vector2D(nextX, 0.0);\n        } else {\n            PMASTERNODE->size     = WORKAREA.size();\n            PMASTERNODE->position = WORKAREA.pos();\n        }\n\n        PMASTERNODE->pTarget->setPositionGlobal({PMASTERNODE->position, PMASTERNODE->size});\n        return;\n    } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) {\n        const float HEIGHT      = STACKWINDOWS != 0 ? WORKAREA.h * PMASTERNODE->percMaster : WORKAREA.h;\n        float       widthLeft   = WORKAREA.w;\n        int         mastersLeft = MASTERS;\n        float       nextX       = 0;\n        float       nextY       = 0;\n\n        if (orientation == ORIENTATION_BOTTOM)\n            nextY = WORKAREA.h - HEIGHT;\n\n        for (auto& nd : m_masterNodesData) {\n            if (!nd->isMaster)\n                continue;\n\n            float WIDTH = mastersLeft > 1 ? widthLeft / mastersLeft * nd->percSize : widthLeft;\n            if (WIDTH > widthLeft * 0.9f && mastersLeft > 1)\n                WIDTH = widthLeft * 0.9f;\n\n            if (*PSMARTRESIZING) {\n                nd->percSize *= WORKAREA.w / masterAccumulatedSize;\n                WIDTH = masterAverageSize * nd->percSize;\n            }\n\n            nd->size     = Vector2D(WIDTH, HEIGHT);\n            nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);\n            nd->pTarget->setPositionGlobal({nd->position, nd->size});\n\n            mastersLeft--;\n            widthLeft -= WIDTH;\n            nextX += WIDTH;\n        }\n    } else { // orientation left, right or center\n        const float TOTAL_WIDTH = *PIGNORERESERVED && centerMasterWindow ? UNRESERVED_WIDTH : WORKAREA.w;\n        float       WIDTH       = TOTAL_WIDTH;\n        float       heightLeft  = WORKAREA.h;\n        int         mastersLeft = MASTERS;\n        float       nextX       = 0;\n        float       nextY       = 0;\n\n        if (STACKWINDOWS > 0 || centerMasterWindow)\n            WIDTH *= PMASTERNODE->percMaster;\n\n        if (orientation == ORIENTATION_RIGHT)\n            nextX = WORKAREA.w - WIDTH;\n        else if (centerMasterWindow)\n            nextX += (TOTAL_WIDTH - WIDTH) / 2;\n\n        for (auto& nd : m_masterNodesData) {\n            if (!nd->isMaster)\n                continue;\n\n            float HEIGHT = mastersLeft > 1 ? heightLeft / mastersLeft * nd->percSize : heightLeft;\n            if (HEIGHT > heightLeft * 0.9f && mastersLeft > 1)\n                HEIGHT = heightLeft * 0.9f;\n\n            if (*PSMARTRESIZING) {\n                nd->percSize *= WORKAREA.h / masterAccumulatedSize;\n                HEIGHT = masterAverageSize * nd->percSize;\n            }\n\n            nd->size     = Vector2D(WIDTH, HEIGHT);\n            nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(reservedLeft, 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY);\n            nd->pTarget->setPositionGlobal({nd->position, nd->size});\n\n            mastersLeft--;\n            heightLeft -= HEIGHT;\n            nextY += HEIGHT;\n        }\n    }\n\n    if (STACKWINDOWS == 0)\n        return;\n\n    // compute placement of slave window(s)\n    int slavesLeft = STACKWINDOWS;\n    if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) {\n        const float HEIGHT    = WORKAREA.h - PMASTERNODE->size.y;\n        float       widthLeft = WORKAREA.w;\n        float       nextX     = 0;\n        float       nextY     = 0;\n\n        if (orientation == ORIENTATION_TOP)\n            nextY = PMASTERNODE->size.y;\n\n        for (auto& nd : m_masterNodesData) {\n            if (nd->isMaster)\n                continue;\n\n            float WIDTH = slavesLeft > 1 ? widthLeft / slavesLeft * nd->percSize : widthLeft;\n            if (WIDTH > widthLeft * 0.9f && slavesLeft > 1)\n                WIDTH = widthLeft * 0.9f;\n\n            if (*PSMARTRESIZING) {\n                nd->percSize *= WORKAREA.w / slaveAccumulatedSize;\n                WIDTH = slaveAverageSize * nd->percSize;\n            }\n\n            nd->size     = Vector2D(WIDTH, HEIGHT);\n            nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);\n            nd->pTarget->setPositionGlobal({nd->position, nd->size});\n\n            slavesLeft--;\n            widthLeft -= WIDTH;\n            nextX += WIDTH;\n        }\n    } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) {\n        const float WIDTH      = WORKAREA.w - PMASTERNODE->size.x;\n        float       heightLeft = WORKAREA.h;\n        float       nextY      = 0;\n        float       nextX      = 0;\n\n        if (orientation == ORIENTATION_LEFT)\n            nextX = PMASTERNODE->size.x;\n\n        for (auto& nd : m_masterNodesData) {\n            if (nd->isMaster)\n                continue;\n\n            float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft;\n            if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1)\n                HEIGHT = heightLeft * 0.9f;\n\n            if (*PSMARTRESIZING) {\n                nd->percSize *= WORKAREA.h / slaveAccumulatedSize;\n                HEIGHT = slaveAverageSize * nd->percSize;\n            }\n\n            nd->size     = Vector2D(WIDTH, HEIGHT);\n            nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);\n            nd->pTarget->setPositionGlobal({nd->position, nd->size});\n\n            slavesLeft--;\n            heightLeft -= HEIGHT;\n            nextY += HEIGHT;\n        }\n    } else { // slaves for centered master window(s)\n        const float WIDTH       = ((*PIGNORERESERVED ? UNRESERVED_WIDTH : WORKAREA.w) - PMASTERNODE->size.x) / 2.0;\n        float       heightLeft  = 0;\n        float       heightLeftL = WORKAREA.h;\n        float       heightLeftR = WORKAREA.h;\n        float       nextX       = 0;\n        float       nextY       = 0;\n        float       nextYL      = 0;\n        float       nextYR      = 0;\n        bool        onRight     = *CMFALLBACK == \"right\";\n        int         slavesLeftL = 1 + (slavesLeft - 1) / 2;\n        int         slavesLeftR = slavesLeft - slavesLeftL;\n\n        if (onRight) {\n            slavesLeftR = 1 + (slavesLeft - 1) / 2;\n            slavesLeftL = slavesLeft - slavesLeftR;\n        }\n\n        const float slaveAverageHeightL     = WORKAREA.h / slavesLeftL;\n        const float slaveAverageHeightR     = WORKAREA.h / slavesLeftR;\n        float       slaveAccumulatedHeightL = 0;\n        float       slaveAccumulatedHeightR = 0;\n\n        if (*PSMARTRESIZING) {\n            for (auto const& nd : m_masterNodesData) {\n                if (nd->isMaster)\n                    continue;\n\n                if (onRight)\n                    slaveAccumulatedHeightR += slaveAverageHeightR * nd->percSize;\n                else\n                    slaveAccumulatedHeightL += slaveAverageHeightL * nd->percSize;\n\n                onRight = !onRight;\n            }\n\n            onRight = *CMFALLBACK == \"right\";\n        }\n\n        for (auto& nd : m_masterNodesData) {\n            if (nd->isMaster)\n                continue;\n\n            if (onRight) {\n                nextX      = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? reservedLeft : 0);\n                nextY      = nextYR;\n                heightLeft = heightLeftR;\n                slavesLeft = slavesLeftR;\n            } else {\n                nextX      = 0;\n                nextY      = nextYL;\n                heightLeft = heightLeftL;\n                slavesLeft = slavesLeftL;\n            }\n\n            float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft;\n            if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1)\n                HEIGHT = heightLeft * 0.9f;\n\n            if (*PSMARTRESIZING) {\n                if (onRight) {\n                    nd->percSize *= WORKAREA.h / slaveAccumulatedHeightR;\n                    HEIGHT = slaveAverageHeightR * nd->percSize;\n                } else {\n                    nd->percSize *= WORKAREA.h / slaveAccumulatedHeightL;\n                    HEIGHT = slaveAverageHeightL * nd->percSize;\n                }\n            }\n\n            nd->size     = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? reservedRight : reservedLeft)) : WIDTH, HEIGHT);\n            nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);\n            nd->pTarget->setPositionGlobal({nd->position, nd->size});\n\n            if (onRight) {\n                heightLeftR -= HEIGHT;\n                nextYR += HEIGHT;\n                slavesLeftR--;\n            } else {\n                heightLeftL -= HEIGHT;\n                nextYL += HEIGHT;\n                slavesLeftL--;\n            }\n\n            onRight = !onRight;\n        }\n    }\n}\n\nSP<ITarget> CMasterAlgorithm::getNextCandidate(SP<ITarget> old) {\n    const auto MIDDLE = old->position().middle();\n\n    if (const auto NODE = getClosestNode(MIDDLE); NODE)\n        return NODE->pTarget.lock();\n\n    if (const auto NODE = getMasterNode(); NODE)\n        return NODE->pTarget.lock();\n\n    return nullptr;\n}\n\nSP<ITarget> CMasterAlgorithm::getNextTarget(SP<ITarget> t, bool next, bool loop) {\n    if (!t || t->floating())\n        return nullptr;\n\n    const auto PNODE = getNodeFromTarget(t);\n\n    auto       nodes = m_masterNodesData;\n    if (!next)\n        std::ranges::reverse(nodes);\n\n    const auto NODEIT = std::ranges::find(nodes, PNODE);\n\n    const bool ISMASTER = PNODE->isMaster;\n\n    auto       CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != PNODE && ISMASTER == other->isMaster; });\n    if (CANDIDATE == nodes.end())\n        CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != PNODE && ISMASTER != other->isMaster; });\n\n    if (CANDIDATE != nodes.end() && !loop) {\n        if ((*CANDIDATE)->isMaster && next)\n            return nullptr;\n        if (!(*CANDIDATE)->isMaster && ISMASTER && !next)\n            return nullptr;\n    }\n\n    return CANDIDATE == nodes.end() ? nullptr : (*CANDIDATE)->pTarget.lock();\n}\n\nint CMasterAlgorithm::getMastersNo() {\n    return std::ranges::count_if(m_masterNodesData, [](const auto& n) { return n->isMaster; });\n}\n\nbool CMasterAlgorithm::isWindowTiled(PHLWINDOW x) {\n    return x && !x->layoutTarget()->floating();\n}\n\nSP<SMasterNodeData> CMasterAlgorithm::getClosestNode(const Vector2D& point) {\n    SP<SMasterNodeData> res         = nullptr;\n    double              distClosest = -1;\n    for (auto& n : m_masterNodesData) {\n        if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) {\n            auto distAnother = vecToRectDistanceSquared(point, n->position, n->position + n->size);\n            if (!res || distAnother < distClosest) {\n                res         = n;\n                distClosest = distAnother;\n            }\n        }\n    }\n    return res;\n}\n"
  },
  {
    "path": "src/layout/algorithm/tiled/master/MasterAlgorithm.hpp",
    "content": "#include \"../../TiledAlgorithm.hpp\"\n\n#include <hyprutils/string/VarList2.hpp>\n\nnamespace Layout {\n    class CAlgorithm;\n}\n\nnamespace Layout::Tiled {\n    struct SMasterNodeData;\n\n    //orientation determines which side of the screen the master area resides\n    enum eOrientation : uint8_t {\n        ORIENTATION_LEFT = 0,\n        ORIENTATION_TOP,\n        ORIENTATION_RIGHT,\n        ORIENTATION_BOTTOM,\n        ORIENTATION_CENTER\n    };\n\n    struct SMasterWorkspaceData {\n        WORKSPACEID                 workspaceID = WORKSPACE_INVALID;\n        std::optional<eOrientation> explicitOrientation;\n        // Previously focused non-master window when `focusmaster previous` command was issued\n        WP<ITarget> focusMasterPrev;\n\n        //\n        bool operator==(const SMasterWorkspaceData& rhs) const {\n            return workspaceID == rhs.workspaceID;\n        }\n    };\n\n    class CMasterAlgorithm : public ITiledAlgorithm {\n      public:\n        CMasterAlgorithm()          = default;\n        virtual ~CMasterAlgorithm() = default;\n\n        virtual void                             newTarget(SP<ITarget> target);\n        virtual void                             movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual void                             removeTarget(SP<ITarget> target);\n\n        virtual void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        virtual void                             recalculate();\n\n        virtual SP<ITarget>                      getNextCandidate(SP<ITarget> old);\n\n        virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n        virtual std::optional<Vector2D>          predictSizeForNewTarget();\n\n        virtual void                             swapTargets(SP<ITarget> a, SP<ITarget> b);\n        virtual void                             moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n      private:\n        std::vector<SP<SMasterNodeData>> m_masterNodesData;\n        SMasterWorkspaceData             m_workspaceData;\n\n        void                             addTarget(SP<ITarget> target, bool firstMap);\n\n        bool                             m_forceWarps = false;\n\n        void                             buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, Hyprutils::String::CVarList2* vars);\n        void                             buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle);\n        void                             runOrientationCycle(Hyprutils::String::CVarList2* vars, int next);\n        eOrientation                     getDynamicOrientation();\n        int                              getNodesNo();\n        SP<SMasterNodeData>              getNodeFromWindow(PHLWINDOW);\n        SP<SMasterNodeData>              getNodeFromTarget(SP<ITarget>);\n        SP<SMasterNodeData>              getMasterNode();\n        SP<SMasterNodeData>              getClosestNode(const Vector2D&);\n        void                             calculateWorkspace();\n        SP<ITarget>                      getNextTarget(SP<ITarget>, bool, bool);\n        int                              getMastersNo();\n        bool                             isWindowTiled(PHLWINDOW);\n        eOrientation                     defaultOrientation();\n    };\n};"
  },
  {
    "path": "src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp",
    "content": "#include \"MonocleAlgorithm.hpp\"\n\n#include \"../../Algorithm.hpp\"\n#include \"../../../space/Space.hpp\"\n#include \"../../../target/WindowTarget.hpp\"\n#include \"../../../LayoutManager.hpp\"\n\n#include \"../../../../config/ConfigValue.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../desktop/history/WindowHistoryTracker.hpp\"\n#include \"../../../../helpers/Monitor.hpp\"\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../event/EventBus.hpp\"\n\n#include <hyprutils/string/VarList2.hpp>\n#include <hyprutils/string/ConstVarList.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Utils;\nusing namespace Layout;\nusing namespace Layout::Tiled;\n\nCMonocleAlgorithm::CMonocleAlgorithm() {\n    // hook into focus changes to bring focused window to front\n    m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) {\n        if (!pWindow)\n            return;\n\n        if (!pWindow->m_workspace->isVisible())\n            return;\n\n        const auto TARGET = pWindow->layoutTarget();\n        if (!TARGET)\n            return;\n\n        focusTargetUpdate(TARGET);\n    });\n}\n\nCMonocleAlgorithm::~CMonocleAlgorithm() {\n    // unhide all windows before destruction\n    for (const auto& data : m_targetDatas) {\n        const auto TARGET = data->target.lock();\n        if (!TARGET)\n            continue;\n\n        const auto WINDOW = TARGET->window();\n        if (WINDOW)\n            WINDOW->setHidden(false);\n    }\n\n    m_focusCallback.reset();\n}\n\nSP<SMonocleTargetData> CMonocleAlgorithm::dataFor(SP<ITarget> t) {\n    for (auto& data : m_targetDatas) {\n        if (data->target.lock() == t)\n            return data;\n    }\n    return nullptr;\n}\n\nvoid CMonocleAlgorithm::newTarget(SP<ITarget> target) {\n    const auto DATA = m_targetDatas.emplace_back(makeShared<SMonocleTargetData>(target));\n\n    m_currentVisibleIndex = m_targetDatas.size() - 1;\n\n    recalculate();\n}\n\nvoid CMonocleAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {\n    newTarget(target);\n}\n\nvoid CMonocleAlgorithm::removeTarget(SP<ITarget> target) {\n    auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; });\n\n    if (it == m_targetDatas.end())\n        return;\n\n    // unhide window when removing from monocle layout\n    const auto WINDOW = target->window();\n    if (WINDOW)\n        WINDOW->setHidden(false);\n\n    const auto INDEX = std::distance(m_targetDatas.begin(), it);\n    m_targetDatas.erase(it);\n\n    if (m_targetDatas.empty()) {\n        m_currentVisibleIndex = 0;\n        return;\n    }\n\n    // try to use the last window in history if we can\n    for (const auto& historyWindow : Desktop::History::windowTracker()->historyForWorkspace(m_parent->space()->workspace()) | std::views::reverse) {\n        auto it = std::ranges::find_if(m_targetDatas, [&historyWindow](const auto& d) { return d->target == historyWindow->layoutTarget(); });\n\n        if (it == m_targetDatas.end())\n            continue;\n\n        // we found a historical target, use that first\n        m_currentVisibleIndex = std::distance(m_targetDatas.begin(), it);\n\n        recalculate();\n\n        return;\n    }\n\n    // if we didn't find history, fall back to last\n\n    if (m_currentVisibleIndex >= (int)m_targetDatas.size())\n        m_currentVisibleIndex = m_targetDatas.size() - 1;\n    else if (INDEX <= m_currentVisibleIndex && m_currentVisibleIndex > 0)\n        m_currentVisibleIndex--;\n\n    recalculate();\n}\n\nvoid CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    // monocle layout doesn't support manual resizing, all windows are fullscreen\n}\n\nvoid CMonocleAlgorithm::recalculate() {\n    if (m_targetDatas.empty())\n        return;\n\n    const auto WORK_AREA = m_parent->space()->workArea();\n\n    for (size_t i = 0; i < m_targetDatas.size(); ++i) {\n        const auto& DATA   = m_targetDatas[i];\n        const auto  TARGET = DATA->target.lock();\n\n        if (!TARGET)\n            continue;\n\n        const auto WINDOW = TARGET->window();\n        if (!WINDOW)\n            continue;\n\n        DATA->layoutBox = WORK_AREA;\n        TARGET->setPositionGlobal(WORK_AREA);\n\n        const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex);\n        WINDOW->setHidden(!SHOULD_BE_VISIBLE);\n    }\n}\n\nSP<ITarget> CMonocleAlgorithm::getNextCandidate(SP<ITarget> old) {\n    if (m_targetDatas.empty())\n        return nullptr;\n\n    auto it = std::ranges::find_if(m_targetDatas, [old](const auto& data) { return data->target.lock() == old; });\n\n    if (it == m_targetDatas.end()) {\n        if (m_currentVisibleIndex >= 0 && m_currentVisibleIndex < (int)m_targetDatas.size())\n            return m_targetDatas[m_currentVisibleIndex]->target.lock();\n        return nullptr;\n    }\n\n    auto next = std::next(it);\n    if (next == m_targetDatas.end())\n        next = m_targetDatas.begin();\n\n    return next->get()->target.lock();\n}\n\nstd::expected<void, std::string> CMonocleAlgorithm::layoutMsg(const std::string_view& sv) {\n    CVarList2 vars(std::string{sv}, 0, 's');\n\n    if (vars.size() < 1)\n        return std::unexpected(\"layoutmsg requires at least 1 argument\");\n\n    const auto COMMAND = vars[0];\n\n    if (COMMAND == \"cyclenext\") {\n        cycleNext();\n        return {};\n    } else if (COMMAND == \"cycleprev\") {\n        cyclePrev();\n        return {};\n    }\n\n    return std::unexpected(std::format(\"Unknown monocle layoutmsg: {}\", COMMAND));\n}\n\nstd::optional<Vector2D> CMonocleAlgorithm::predictSizeForNewTarget() {\n    const auto WORK_AREA = m_parent->space()->workArea();\n    return WORK_AREA.size();\n}\n\nvoid CMonocleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {\n    auto nodeA = dataFor(a);\n    auto nodeB = dataFor(b);\n\n    if (nodeA)\n        nodeA->target = b;\n    if (nodeB)\n        nodeB->target = a;\n\n    recalculate();\n}\n\nvoid CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>(\"binds:window_direction_monitor_fallback\");\n\n    if (!*PMONITORFALLBACK)\n        return; // noop\n\n    // try to find a monitor in the specified direction, thats the logical thing\n    if (!t || !t->space() || !t->space()->workspace())\n        return;\n\n    const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir);\n\n    // if we found a monitor, move the window there\n    if (PMONINDIR && PMONINDIR != t->space()->workspace()->m_monitor.lock()) {\n        const auto TARGETWS = PMONINDIR->m_activeWorkspace;\n\n        if (t->window())\n            t->window()->setAnimationsToMove();\n\n        t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir));\n    }\n}\n\nvoid CMonocleAlgorithm::cycleNext() {\n    if (m_targetDatas.empty())\n        return;\n\n    m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size();\n    updateVisible();\n}\n\nvoid CMonocleAlgorithm::cyclePrev() {\n    if (m_targetDatas.empty())\n        return;\n\n    m_currentVisibleIndex--;\n    if (m_currentVisibleIndex < 0)\n        m_currentVisibleIndex = m_targetDatas.size() - 1;\n    updateVisible();\n}\n\nvoid CMonocleAlgorithm::focusTargetUpdate(SP<ITarget> target) {\n    auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; });\n\n    if (it == m_targetDatas.end())\n        return;\n\n    const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it);\n\n    if (m_currentVisibleIndex != NEW_INDEX) {\n        m_currentVisibleIndex = NEW_INDEX;\n        updateVisible();\n    }\n}\n\nvoid CMonocleAlgorithm::updateVisible() {\n    recalculate();\n\n    const auto VISIBLE_TARGET = getVisibleTarget();\n    if (!VISIBLE_TARGET)\n        return;\n\n    const auto WINDOW = VISIBLE_TARGET->window();\n    if (!WINDOW)\n        return;\n\n    Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);\n}\n\nSP<ITarget> CMonocleAlgorithm::getVisibleTarget() {\n    if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size())\n        return nullptr;\n\n    return m_targetDatas[m_currentVisibleIndex]->target.lock();\n}\n"
  },
  {
    "path": "src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp",
    "content": "#pragma once\n\n#include \"../../TiledAlgorithm.hpp\"\n#include \"../../../../helpers/signal/Signal.hpp\"\n\n#include <vector>\n\nnamespace Layout::Tiled {\n\n    struct SMonocleTargetData {\n        SMonocleTargetData(SP<ITarget> t) : target(t) {\n            ;\n        }\n\n        WP<ITarget> target;\n        CBox        layoutBox;\n    };\n\n    class CMonocleAlgorithm : public ITiledAlgorithm {\n      public:\n        CMonocleAlgorithm();\n        virtual ~CMonocleAlgorithm();\n\n        virtual void                             newTarget(SP<ITarget> target);\n        virtual void                             movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual void                             removeTarget(SP<ITarget> target);\n\n        virtual void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        virtual void                             recalculate();\n\n        virtual SP<ITarget>                      getNextCandidate(SP<ITarget> old);\n\n        virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n        virtual std::optional<Vector2D>          predictSizeForNewTarget();\n\n        virtual void                             swapTargets(SP<ITarget> a, SP<ITarget> b);\n        virtual void                             moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n      private:\n        std::vector<SP<SMonocleTargetData>> m_targetDatas;\n        CHyprSignalListener                 m_focusCallback;\n\n        int                                 m_currentVisibleIndex = 0;\n\n        SP<SMonocleTargetData>              dataFor(SP<ITarget> t);\n        void                                cycleNext();\n        void                                cyclePrev();\n        void                                focusTargetUpdate(SP<ITarget> target);\n        void                                updateVisible();\n        SP<ITarget>                         getVisibleTarget();\n    };\n};\n"
  },
  {
    "path": "src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp",
    "content": "#include \"ScrollTapeController.hpp\"\n#include \"ScrollingAlgorithm.hpp\"\n#include <algorithm>\n#include <cmath>\n\nusing namespace Layout::Tiled;\n\nCScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) {\n    ;\n}\n\nvoid CScrollTapeController::setDirection(eScrollDirection dir) {\n    m_direction = dir;\n}\n\neScrollDirection CScrollTapeController::getDirection() const {\n    return m_direction;\n}\n\nbool CScrollTapeController::isPrimaryHorizontal() const {\n    return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT;\n}\n\nbool CScrollTapeController::isReversed() const {\n    return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP;\n}\n\nsize_t CScrollTapeController::stripCount() const {\n    return m_strips.size();\n}\n\nSStripData& CScrollTapeController::getStrip(size_t index) {\n    return m_strips[index];\n}\n\nconst SStripData& CScrollTapeController::getStrip(size_t index) const {\n    return m_strips[index];\n}\n\nvoid CScrollTapeController::setOffset(double offset) {\n    m_offset = offset;\n}\n\ndouble CScrollTapeController::getOffset() const {\n    return m_offset;\n}\n\nvoid CScrollTapeController::adjustOffset(double delta) {\n    m_offset += delta;\n}\n\nsize_t CScrollTapeController::addStrip(float size) {\n    m_strips.emplace_back();\n    m_strips.back().size = size;\n    return m_strips.size() - 1;\n}\n\nvoid CScrollTapeController::insertStrip(ssize_t afterIndex, float size) {\n    if (afterIndex >= sc<ssize_t>(m_strips.size())) {\n        addStrip(size);\n        return;\n    }\n\n    afterIndex = std::clamp(afterIndex, sc<ssize_t>(-1L), sc<ssize_t>(INT32_MAX));\n\n    SStripData newStrip;\n    newStrip.size = size;\n    m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);\n}\n\nvoid CScrollTapeController::removeStrip(size_t index) {\n    if (index < m_strips.size())\n        m_strips.erase(m_strips.begin() + index);\n}\n\ndouble CScrollTapeController::getPrimary(const Vector2D& v) const {\n    return isPrimaryHorizontal() ? v.x : v.y;\n}\n\ndouble CScrollTapeController::getSecondary(const Vector2D& v) const {\n    return isPrimaryHorizontal() ? v.y : v.x;\n}\n\nvoid CScrollTapeController::setPrimary(Vector2D& v, double val) const {\n    if (isPrimaryHorizontal())\n        v.x = val;\n    else\n        v.y = val;\n}\n\nvoid CScrollTapeController::setSecondary(Vector2D& v, double val) const {\n    if (isPrimaryHorizontal())\n        v.y = val;\n    else\n        v.x = val;\n}\n\nVector2D CScrollTapeController::makeVector(double primary, double secondary) const {\n    if (isPrimaryHorizontal())\n        return {primary, secondary};\n    else\n        return {secondary, primary};\n}\n\ndouble CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const {\n    if (m_strips.empty())\n        return 0.0;\n\n    if (fullscreenOnOne && m_strips.size() == 1)\n        return getPrimary(usableArea.size());\n\n    double       total         = 0.0;\n    const double usablePrimary = getPrimary(usableArea.size());\n\n    for (const auto& strip : m_strips) {\n        total += usablePrimary * strip.size;\n    }\n\n    return total;\n}\n\ndouble CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {\n    if (stripIndex >= m_strips.size())\n        return 0.0;\n\n    const double usablePrimary = getPrimary(usableArea.size());\n    double       current       = 0.0;\n\n    for (size_t i = 0; i < stripIndex; ++i) {\n        const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size;\n        current += stripSize;\n    }\n\n    return current;\n}\n\ndouble CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {\n    if (stripIndex >= m_strips.size())\n        return 0.0;\n\n    const double usablePrimary = getPrimary(usableArea.size());\n\n    if (fullscreenOnOne && m_strips.size() == 1)\n        return usablePrimary;\n\n    return usablePrimary * m_strips[stripIndex].size;\n}\n\nCBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {\n    if (stripIndex >= m_strips.size())\n        return {};\n\n    const auto& strip = m_strips[stripIndex];\n    if (targetIndex >= strip.targetSizes.size())\n        return {};\n\n    const double usableSecondary = getSecondary(usableArea.size());\n    const double usablePrimary   = getPrimary(usableArea.size());\n    const double cameraOffset    = calculateCameraOffset(usableArea, fullscreenOnOne);\n\n    // calculate position along primary axis (strip position)\n    double primaryPos  = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);\n    double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);\n\n    // calculate position along secondary axis (within strip)\n    double secondaryPos = 0.0;\n    for (size_t i = 0; i < targetIndex; ++i) {\n        secondaryPos += strip.targetSizes[i] * usableSecondary;\n    }\n    double secondarySize = strip.targetSizes[targetIndex] * usableSecondary;\n\n    // apply camera offset based on direction\n    // for RIGHT/DOWN: scroll offset moves content left/up (subtract)\n    // for LEFT/UP: scroll offset moves content right/down (different coordinate system)\n    if (m_direction == SCROLL_DIR_LEFT) {\n        // LEFT: flip the entire primary axis, then apply offset\n        primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;\n    } else if (m_direction == SCROLL_DIR_UP) {\n        // UP: flip the entire primary axis, then apply offset\n        primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;\n    } else {\n        // RIGHT/DOWN: normal offset\n        primaryPos -= cameraOffset;\n    }\n\n    // create the box in primary/secondary coordinates\n    Vector2D pos  = makeVector(primaryPos, secondaryPos);\n    Vector2D size = makeVector(primarySize, secondarySize);\n\n    // translate to workspace position\n    pos = pos + workspaceOffset;\n\n    return CBox{pos, size};\n}\n\ndouble CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) {\n    const double maxExtent     = calculateMaxExtent(usableArea, fullscreenOnOne);\n    const double usablePrimary = getPrimary(usableArea.size());\n\n    // don't adjust the offset if we are dragging\n    if (isBeingDragged())\n        return m_offset;\n\n    // if the content fits in viewport, center it\n    if (maxExtent < usablePrimary)\n        m_offset = std::round((maxExtent - usablePrimary) / 2.0);\n\n    // if the offset is negative but we already extended, reset offset to 0\n    if (maxExtent > usablePrimary && m_offset < 0.0)\n        m_offset = 0.0;\n\n    return m_offset;\n}\n\nVector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) {\n    const double offset = calculateCameraOffset(usableArea, fullscreenOnOne);\n\n    if (isReversed())\n        return makeVector(offset, 0.0);\n    else\n        return makeVector(-offset, 0.0);\n}\n\nvoid CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {\n    if (stripIndex >= m_strips.size())\n        return;\n\n    const double usablePrimary = getPrimary(usableArea.size());\n    const double stripStart    = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);\n    const double stripSize     = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);\n\n    m_offset = stripStart - (usablePrimary - stripSize) / 2.0;\n}\n\nvoid CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {\n    if (stripIndex >= m_strips.size())\n        return;\n\n    const double usablePrimary = getPrimary(usableArea.size());\n    const double stripStart    = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);\n    const double stripSize     = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);\n\n    const double lo = stripStart - usablePrimary + stripSize;\n    const double hi = stripStart;\n\n    if (lo > hi) {\n        // strip is wider than viewport (e.g. during monitor reconnection after suspend),\n        // center the strip instead of hitting the std::clamp assertion\n        m_offset = stripStart - (usablePrimary - stripSize) / 2.0;\n        return;\n    }\n\n    m_offset = std::clamp(m_offset, lo, hi);\n}\n\nbool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne, bool full) const {\n    if (stripIndex >= m_strips.size())\n        return false;\n\n    const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);\n    const double stripEnd   = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne);\n    const double viewStart  = m_offset;\n    const double viewEnd    = m_offset + getPrimary(usableArea.size());\n\n    if (!full)\n        return stripStart < viewEnd && viewStart < stripEnd;\n    else\n        return stripStart >= viewStart && stripEnd <= viewEnd;\n}\n\nsize_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const {\n    if (m_strips.empty())\n        return 0;\n\n    const double usablePrimary = getPrimary(usableArea.size());\n    double       currentPos    = m_offset;\n\n    for (size_t i = 0; i < m_strips.size(); ++i) {\n        const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne);\n        currentPos += stripSize;\n\n        if (currentPos >= usablePrimary / 2.0 - 2.0)\n            return i;\n    }\n\n    return m_strips.empty() ? 0 : m_strips.size() - 1;\n}\n\nvoid CScrollTapeController::swapStrips(size_t a, size_t b) {\n    if (a >= m_strips.size() || b >= m_strips.size())\n        return;\n\n    std::swap(m_strips.at(a), m_strips.at(b));\n}\n\nbool CScrollTapeController::isBeingDragged() const {\n    for (const auto& s : m_strips) {\n        if (!s.userData)\n            continue;\n\n        for (const auto& d : s.userData->targetDatas) {\n            if (d->target == g_layoutManager->dragController()->target())\n                return true;\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp",
    "content": "#pragma once\n\n#include \"../../../../helpers/math/Math.hpp\"\n#include \"../../../../helpers/memory/Memory.hpp\"\n#include <vector>\n\nnamespace Layout::Tiled {\n\n    struct SColumnData;\n\n    enum eScrollDirection : uint8_t {\n        SCROLL_DIR_RIGHT = 0,\n        SCROLL_DIR_LEFT,\n        SCROLL_DIR_DOWN,\n        SCROLL_DIR_UP,\n    };\n\n    struct SStripData {\n        float              size = 1.F;  // size along primary axis\n        std::vector<float> targetSizes; // sizes along secondary axis for each target in this strip\n        WP<SColumnData>    userData;\n\n        SStripData() = default;\n    };\n\n    struct STapeLayoutResult {\n        CBox   box;\n        size_t stripIndex  = 0;\n        size_t targetIndex = 0;\n    };\n\n    class CScrollTapeController {\n      public:\n        CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT);\n        ~CScrollTapeController() = default;\n\n        void              setDirection(eScrollDirection dir);\n        eScrollDirection  getDirection() const;\n        bool              isPrimaryHorizontal() const;\n        bool              isReversed() const;\n\n        size_t            addStrip(float size = 1.0F);\n        void              insertStrip(ssize_t afterIndex, float size = 1.0F);\n        void              removeStrip(size_t index);\n        size_t            stripCount() const;\n        SStripData&       getStrip(size_t index);\n        const SStripData& getStrip(size_t index) const;\n        void              swapStrips(size_t a, size_t b);\n\n        void              setOffset(double offset);\n        double            getOffset() const;\n        void              adjustOffset(double delta);\n\n        double            calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const;\n        double            calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;\n        double            calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;\n\n        CBox              calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false);\n\n        double            calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false);\n        Vector2D          getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false);\n\n        void              centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);\n        void              fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);\n\n        bool              isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false, bool full = false) const;\n\n        size_t            getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const;\n\n      private:\n        eScrollDirection        m_direction = SCROLL_DIR_RIGHT;\n        std::vector<SStripData> m_strips;\n        double                  m_offset = 0.0;\n\n        double                  getPrimary(const Vector2D& v) const;\n        double                  getSecondary(const Vector2D& v) const;\n        void                    setPrimary(Vector2D& v, double val) const;\n        void                    setSecondary(Vector2D& v, double val) const;\n        bool                    isBeingDragged() const;\n\n        Vector2D                makeVector(double primary, double secondary) const;\n    };\n};\n"
  },
  {
    "path": "src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp",
    "content": "#include \"ScrollingAlgorithm.hpp\"\n#include \"ScrollTapeController.hpp\"\n\n#include \"../../Algorithm.hpp\"\n#include \"../../../space/Space.hpp\"\n#include \"../../../LayoutManager.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../config/ConfigValue.hpp\"\n#include \"../../../../config/ConfigManager.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n#include \"../../../../managers/input/InputManager.hpp\"\n#include \"../../../../event/EventBus.hpp\"\n\n#include <hyprutils/string/VarList2.hpp>\n#include <hyprutils/string/ConstVarList.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Utils;\nusing namespace Layout;\nusing namespace Layout::Tiled;\n\nconstexpr float MIN_COLUMN_WIDTH = 0.05F;\nconstexpr float MAX_COLUMN_WIDTH = 1.F;\nconstexpr float MIN_ROW_HEIGHT   = 0.1F;\nconstexpr float MAX_ROW_HEIGHT   = 1.F;\n\n//\nfloat SColumnData::getColumnWidth() const {\n    if (!scrollingData || !scrollingData->controller)\n        return 1.F;\n\n    auto sd = scrollingData.lock();\n    if (!sd)\n        return 1.F;\n\n    int64_t idx = sd->idx(self.lock());\n    if (idx < 0 || (size_t)idx >= sd->controller->stripCount())\n        return 1.F;\n\n    return sd->controller->getStrip(idx).size;\n}\n\nvoid SColumnData::setColumnWidth(float width) {\n    if (!scrollingData || !scrollingData->controller)\n        return;\n\n    auto sd = scrollingData.lock();\n    if (!sd)\n        return;\n\n    int64_t idx = sd->idx(self.lock());\n    if (idx < 0 || (size_t)idx >= sd->controller->stripCount())\n        return;\n\n    sd->controller->getStrip(idx).size = width;\n}\n\nfloat SColumnData::getTargetSize(size_t idx) const {\n    if (!scrollingData || !scrollingData->controller)\n        return 1.F;\n\n    auto sd = scrollingData.lock();\n    if (!sd)\n        return 1.F;\n\n    int64_t colIdx = sd->idx(self.lock());\n    if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount())\n        return 1.F;\n\n    const auto& strip = sd->controller->getStrip(colIdx);\n    if (idx >= strip.targetSizes.size())\n        return 1.F;\n\n    return strip.targetSizes[idx];\n}\n\nvoid SColumnData::setTargetSize(size_t idx, float size) {\n    if (!scrollingData || !scrollingData->controller)\n        return;\n\n    auto sd = scrollingData.lock();\n    if (!sd)\n        return;\n\n    int64_t colIdx = sd->idx(self.lock());\n    if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount())\n        return;\n\n    auto& strip = sd->controller->getStrip(colIdx);\n    if (idx >= strip.targetSizes.size())\n        strip.targetSizes.resize(idx + 1, 1.F);\n\n    strip.targetSizes[idx] = size;\n}\n\nfloat SColumnData::getTargetSize(SP<SScrollingTargetData> target) const {\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        if (targetDatas[i] == target)\n            return getTargetSize(i);\n    }\n    return 1.F;\n}\n\nvoid SColumnData::setTargetSize(SP<SScrollingTargetData> target, float size) {\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        if (targetDatas[i] == target) {\n            setTargetSize(i, size);\n            return;\n        }\n    }\n}\n\nvoid SColumnData::add(SP<ITarget> t) {\n    const float newSize = 1.F / (float)(targetDatas.size() + 1);\n\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));\n    }\n\n    targetDatas.emplace_back(makeShared<SScrollingTargetData>(t, self.lock()));\n    setTargetSize(targetDatas.size() - 1, newSize);\n}\n\nvoid SColumnData::add(SP<ITarget> t, int after) {\n    const float newSize = 1.F / (float)(targetDatas.size() + 1);\n\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));\n    }\n\n    targetDatas.insert(targetDatas.begin() + after + 1, makeShared<SScrollingTargetData>(t, self.lock()));\n\n    // Sync sizes - need to insert at the right position\n    if (scrollingData) {\n        auto sd = scrollingData.lock();\n        if (sd && sd->controller) {\n            int64_t colIdx = sd->idx(self.lock());\n            if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) {\n                auto& strip = sd->controller->getStrip(colIdx);\n                strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize);\n            }\n        }\n    }\n}\n\nvoid SColumnData::add(SP<SScrollingTargetData> w) {\n    const float newSize = 1.F / (float)(targetDatas.size() + 1);\n\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));\n    }\n\n    targetDatas.emplace_back(w);\n    w->column = self;\n    setTargetSize(targetDatas.size() - 1, newSize);\n}\n\nvoid SColumnData::add(SP<SScrollingTargetData> w, int after) {\n    const float newSize = 1.F / (float)(targetDatas.size() + 1);\n\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1));\n    }\n\n    targetDatas.insert(targetDatas.begin() + after + 1, w);\n    w->column = self;\n\n    // Sync sizes\n    if (scrollingData) {\n        auto sd = scrollingData.lock();\n        if (sd && sd->controller) {\n            int64_t colIdx = sd->idx(self.lock());\n            if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) {\n                auto& strip = sd->controller->getStrip(colIdx);\n                strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize);\n            }\n        }\n    }\n}\n\nsize_t SColumnData::idx(SP<ITarget> t) {\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        if (targetDatas[i]->target == t)\n            return i;\n    }\n    return 0;\n}\n\nsize_t SColumnData::idxForHeight(float y) {\n    if (targetDatas.empty())\n        return 0;\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        if (targetDatas[i]->target->position().y < y)\n            continue;\n        return i == 0 ? 0 : i - 1;\n    }\n    return targetDatas.size() - 1;\n}\n\nvoid SColumnData::remove(SP<ITarget> t) {\n    const auto SIZE_BEFORE = targetDatas.size();\n    size_t     removedIdx  = 0;\n    bool       found       = false;\n\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        if (targetDatas[i]->target == t) {\n            removedIdx = i;\n            found      = true;\n            break;\n        }\n    }\n\n    std::erase_if(targetDatas, [&t](const auto& e) { return e->target == t; });\n\n    if (SIZE_BEFORE == targetDatas.size() && SIZE_BEFORE > 0)\n        return;\n\n    if (found && scrollingData) {\n        auto sd = scrollingData.lock();\n        if (sd && sd->controller) {\n            int64_t colIdx = sd->idx(self.lock());\n            if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) {\n                auto& strip = sd->controller->getStrip(colIdx);\n                if (removedIdx < strip.targetSizes.size()) {\n                    strip.targetSizes.erase(strip.targetSizes.begin() + removedIdx);\n                }\n            }\n        }\n    }\n\n    // Renormalize sizes\n    float newMaxSize = 0.F;\n    for (size_t i = 0; i < targetDatas.size(); ++i) {\n        newMaxSize += getTargetSize(i);\n    }\n\n    if (newMaxSize > 0.F) {\n        for (size_t i = 0; i < targetDatas.size(); ++i) {\n            setTargetSize(i, getTargetSize(i) / newMaxSize);\n        }\n    }\n\n    if (targetDatas.empty() && scrollingData)\n        scrollingData->remove(self.lock());\n}\n\nbool SColumnData::up(SP<SScrollingTargetData> w) {\n    for (size_t i = 1; i < targetDatas.size(); ++i) {\n        if (targetDatas[i] != w)\n            continue;\n\n        std::swap(targetDatas[i], targetDatas[i - 1]);\n        return true;\n    }\n\n    return false;\n}\n\nbool SColumnData::down(SP<SScrollingTargetData> w) {\n    if (targetDatas.empty())\n        return false;\n\n    for (size_t i = 0; i < targetDatas.size() - 1; ++i) {\n        if (targetDatas[i] != w)\n            continue;\n\n        std::swap(targetDatas[i], targetDatas[i + 1]);\n        return true;\n    }\n\n    return false;\n}\n\nSP<SScrollingTargetData> SColumnData::next(SP<SScrollingTargetData> w) {\n    for (size_t i = 0; i < targetDatas.size() - 1; ++i) {\n        if (targetDatas[i] != w)\n            continue;\n\n        return targetDatas[i + 1];\n    }\n\n    return nullptr;\n}\n\nSP<SScrollingTargetData> SColumnData::prev(SP<SScrollingTargetData> w) {\n    for (size_t i = 1; i < targetDatas.size(); ++i) {\n        if (targetDatas[i] != w)\n            continue;\n\n        return targetDatas[i - 1];\n    }\n\n    return nullptr;\n}\n\nbool SColumnData::has(SP<ITarget> t) {\n    return std::ranges::find_if(targetDatas, [t](const auto& e) { return e->target == t; }) != targetDatas.end();\n}\n\nSScrollingData::SScrollingData(CScrollingAlgorithm* algo) : algorithm(algo) {\n    controller = makeUnique<CScrollTapeController>(SCROLL_DIR_RIGHT);\n}\n\nSP<SColumnData> SScrollingData::add(std::optional<float> width) {\n    auto col  = columns.emplace_back(makeShared<SColumnData>(self.lock()));\n    col->self = col;\n\n    size_t stripIdx                         = controller->addStrip(width.value_or(algorithm->defaultColumnWidth()));\n    controller->getStrip(stripIdx).userData = col;\n\n    return col;\n}\n\nSP<SColumnData> SScrollingData::add(int after, std::optional<float> width) {\n    auto col  = makeShared<SColumnData>(self.lock());\n    col->self = col;\n    columns.insert(columns.begin() + after + 1, col);\n\n    controller->insertStrip(after, width.value_or(algorithm->defaultColumnWidth()));\n    controller->getStrip(after + 1).userData = col;\n\n    return col;\n}\n\nint64_t SScrollingData::idx(SP<SColumnData> c) {\n    for (size_t i = 0; i < columns.size(); ++i) {\n        if (columns[i] == c)\n            return i;\n    }\n\n    return -1;\n}\n\nvoid SScrollingData::remove(SP<SColumnData> c) {\n    // find index before removing\n    int64_t index = idx(c);\n\n    std::erase(columns, c);\n\n    // sync with controller\n    if (index >= 0)\n        controller->removeStrip(index);\n}\n\nSP<SColumnData> SScrollingData::next(SP<SColumnData> c) {\n    for (size_t i = 0; i < columns.size(); ++i) {\n        if (columns[i] != c)\n            continue;\n\n        if (i == columns.size() - 1)\n            return nullptr;\n\n        return columns[i + 1];\n    }\n\n    return nullptr;\n}\n\nSP<SColumnData> SScrollingData::prev(SP<SColumnData> c) {\n    for (size_t i = 0; i < columns.size(); ++i) {\n        if (columns[i] != c)\n            continue;\n\n        if (i == 0)\n            return nullptr;\n\n        return columns[i - 1];\n    }\n\n    return nullptr;\n}\n\nvoid SScrollingData::centerCol(SP<SColumnData> c) {\n    if (!c)\n        return;\n\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n    const auto        USABLE   = algorithm->usableArea();\n    int64_t           colIdx   = idx(c);\n\n    if (colIdx >= 0)\n        controller->centerStrip(colIdx, USABLE, *PFSONONE);\n}\n\nvoid SScrollingData::fitCol(SP<SColumnData> c) {\n    if (!c)\n        return;\n\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n    const auto        USABLE   = algorithm->usableArea();\n    int64_t           colIdx   = idx(c);\n\n    if (colIdx >= 0)\n        controller->fitStrip(colIdx, USABLE, *PFSONONE);\n}\n\nvoid SScrollingData::centerOrFitCol(SP<SColumnData> c) {\n    if (!c)\n        return;\n\n    static const auto PFITMETHOD = CConfigValue<Hyprlang::INT>(\"scrolling:focus_fit_method\");\n\n    if (*PFITMETHOD == 1)\n        fitCol(c);\n    else\n        centerCol(c);\n}\n\nSP<SColumnData> SScrollingData::atCenter() {\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n    const auto        USABLE   = algorithm->usableArea();\n\n    size_t            centerIdx = controller->getStripAtCenter(USABLE, *PFSONONE);\n\n    if (centerIdx < columns.size())\n        return columns[centerIdx];\n\n    return nullptr;\n}\n\nvoid SScrollingData::recalculate(bool forceInstant) {\n    if (!algorithm->m_parent || !algorithm->m_parent->space() || !algorithm->m_parent->space()->workspace() || !algorithm->m_parent->space()->workspace()->m_monitor ||\n        algorithm->m_parent->space()->workspace()->m_hasFullscreenWindow)\n        return;\n\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n\n    const CBox        USABLE   = algorithm->usableArea();\n    const auto        WORKAREA = algorithm->m_parent->space()->workArea();\n\n    controller->setDirection(algorithm->getDynamicDirection());\n\n    for (size_t i = 0; i < columns.size(); ++i) {\n        const auto& COL = columns[i];\n\n        for (size_t j = 0; j < COL->targetDatas.size(); ++j) {\n            const auto& TARGET = COL->targetDatas[j];\n\n            TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE);\n\n            if (TARGET->target)\n                TARGET->target->setPositionGlobal(TARGET->layoutBox);\n            if (forceInstant && TARGET->target)\n                TARGET->target->warpPositionSize();\n        }\n    }\n}\n\ndouble SScrollingData::maxWidth() {\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n    const auto        USABLE   = algorithm->usableArea();\n\n    return controller->calculateMaxExtent(USABLE, *PFSONONE);\n}\n\nbool SScrollingData::visible(SP<SColumnData> c, bool full) {\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n    const auto        USABLE   = algorithm->usableArea();\n    int64_t           colIdx   = idx(c);\n\n    if (colIdx >= 0)\n        return controller->isStripVisible(colIdx, USABLE, *PFSONONE, full);\n\n    return false;\n}\n\nCScrollingAlgorithm::CScrollingAlgorithm() {\n    static const auto PCONFWIDTHS    = CConfigValue<Hyprlang::STRING>(\"scrolling:explicit_column_widths\");\n    static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>(\"scrolling:direction\");\n\n    m_scrollingData       = makeShared<SScrollingData>(this);\n    m_scrollingData->self = m_scrollingData;\n\n    // Helper to parse explicit_column_widths string\n    auto parseColumnWidths = [](const std::string& dir) -> std::vector<float> {\n        auto          widthVec = std::vector<float>();\n\n        CConstVarList widths(dir, 0, ',');\n        for (auto& w : widths) {\n            try {\n                widthVec.emplace_back(std::clamp(std::stof(std::string{w}), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH));\n            } catch (...) { Log::logger->log(Log::ERR, \"scrolling: Failed to parse width {} as float\", w); }\n        }\n        if (widthVec.empty())\n            widthVec = {0.333, 0.5, 0.667, 1.0}; // default\n        return widthVec;\n    };\n\n    // Helper to parse direction string\n    auto parseDirection = [](const std::string& dir) -> eScrollDirection {\n        if (dir == \"left\")\n            return SCROLL_DIR_LEFT;\n        else if (dir == \"down\")\n            return SCROLL_DIR_DOWN;\n        else if (dir == \"up\")\n            return SCROLL_DIR_UP;\n        else\n            return SCROLL_DIR_RIGHT; // default\n    };\n\n    m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseColumnWidths, parseDirection] {\n        static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>(\"scrolling:direction\");\n\n        m_config.configuredWidths.clear();\n        m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS);\n\n        // Update scroll direction\n        m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));\n    });\n\n    m_mouseButtonCallback = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent e, Event::SCallbackInfo&) {\n        static const auto PFOLLOW_FOCUS = CConfigValue<Hyprlang::INT>(\"scrolling:follow_focus\");\n\n        if (*PFOLLOW_FOCUS && e.state == WL_POINTER_BUTTON_STATE_RELEASED && Desktop::focusState()->window())\n            focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_CLICK);\n    });\n\n    m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) {\n        if (!pWindow)\n            return;\n\n        static const auto PFOLLOW_FOCUS = CConfigValue<Hyprlang::INT>(\"scrolling:follow_focus\");\n\n        if (!*PFOLLOW_FOCUS && !Desktop::isHardInputFocusReason(reason))\n            return;\n\n        if (pWindow->m_workspace != m_parent->space()->workspace())\n            return;\n\n        const auto TARGET = pWindow->layoutTarget();\n        if (!TARGET || TARGET->floating())\n            return;\n\n        focusOnInput(TARGET, reason == Desktop::FOCUS_REASON_CLICK ? INPUT_MODE_CLICK : (Desktop::isHardInputFocusReason(reason) ? INPUT_MODE_KB : INPUT_MODE_SOFT));\n    });\n\n    // Initialize default widths and direction\n    m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS);\n    m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));\n}\n\nCScrollingAlgorithm::~CScrollingAlgorithm() {\n    m_configCallback.reset();\n    m_focusCallback.reset();\n}\n\nvoid CScrollingAlgorithm::focusOnInput(SP<ITarget> target, eInputMode input) {\n    static const auto PFOLLOW_FOCUS_MIN_PERC = CConfigValue<Hyprlang::FLOAT>(\"scrolling:follow_min_visible\");\n\n    if (!target || target->space() != m_parent->space())\n        return;\n\n    const auto TARGETDATA = dataFor(target);\n    if (!TARGETDATA)\n        return;\n\n    if (*PFOLLOW_FOCUS_MIN_PERC > 0.F && input == INPUT_MODE_SOFT) {\n        // check how much of the window is visible, unless hard input focus\n\n        const auto   IS_HORIZ = m_scrollingData->controller->isPrimaryHorizontal();\n\n        const auto   MON_BOX     = m_parent->space()->workspace()->m_monitor->logicalBox();\n        const auto   TARGET_POS  = target->position();\n        const double VISIBLE_LEN = IS_HORIZ ?                                                                            //\n            std::abs(std::min(MON_BOX.x + MON_BOX.w, TARGET_POS.x + TARGET_POS.w) - (std::max(MON_BOX.x, TARGET_POS.x))) //\n            :\n            std::abs(std::min(MON_BOX.y + MON_BOX.h, TARGET_POS.y + TARGET_POS.h) - (std::max(MON_BOX.y, TARGET_POS.y)));\n\n        // if the amount of visible X is below minimum, reject\n        if (VISIBLE_LEN < (IS_HORIZ ? MON_BOX.w : MON_BOX.h) * std::clamp(*PFOLLOW_FOCUS_MIN_PERC, 0.F, 1.F))\n            return;\n    }\n\n    // if we moved via non-kb, and it's fully visible, ignore\n    if (m_scrollingData->visible(TARGETDATA->column.lock(), true) && input != INPUT_MODE_KB)\n        return;\n\n    static const auto PFITMETHOD = CConfigValue<Hyprlang::INT>(\"scrolling:focus_fit_method\");\n    if (*PFITMETHOD == 1 || input == INPUT_MODE_CLICK)\n        m_scrollingData->fitCol(TARGETDATA->column.lock());\n    else\n        m_scrollingData->centerCol(TARGETDATA->column.lock());\n    m_scrollingData->recalculate();\n}\n\nvoid CScrollingAlgorithm::newTarget(SP<ITarget> target) {\n    auto droppingOn = Desktop::focusState()->window();\n\n    if (droppingOn && droppingOn->layoutTarget() == target)\n        droppingOn = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);\n\n    SP<SScrollingTargetData> droppingData   = droppingOn ? dataFor(droppingOn->layoutTarget()) : nullptr;\n    SP<SColumnData>          droppingColumn = droppingData ? droppingData->column.lock() : nullptr;\n    const auto               width          = target->window()->m_ruleApplicator->static_.scrollingWidth;\n\n    if (!droppingColumn) {\n        auto col = m_scrollingData->add(width);\n        col->add(target);\n        m_scrollingData->fitCol(col);\n    } else {\n        if (g_layoutManager->dragController()->wasDraggingWindow() && g_layoutManager->dragController()->draggingTiled()) {\n            if (droppingOn) {\n                const auto IDX = droppingColumn->idx(droppingOn->layoutTarget());\n                const auto TOP = droppingOn->getWindowIdealBoundingBoxIgnoreReserved().middle().y > g_pInputManager->getMouseCoordsInternal().y;\n                droppingColumn->add(target, TOP ? (IDX == 0 ? -1 : IDX - 1) : (IDX));\n            } else\n                droppingColumn->add(target);\n            m_scrollingData->fitCol(droppingColumn);\n        } else {\n            auto idx = m_scrollingData->idx(droppingColumn);\n            auto col = idx == -1 ? m_scrollingData->add(width) : m_scrollingData->add(idx, width);\n            col->add(target);\n            m_scrollingData->fitCol(col);\n        }\n    }\n\n    m_scrollingData->recalculate();\n}\n\nvoid CScrollingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {\n    newTarget(target);\n}\n\nvoid CScrollingAlgorithm::removeTarget(SP<ITarget> target) {\n    const auto DATA = dataFor(target);\n\n    if (!DATA)\n        return;\n\n    if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) {\n        // move the view if this is the last column\n        const auto   USABLE         = usableArea();\n        const bool   isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal();\n        const double usablePrimary  = isPrimaryHoriz ? USABLE.w : USABLE.h;\n        m_scrollingData->controller->adjustOffset(-(usablePrimary * DATA->column->getColumnWidth()));\n    }\n\n    DATA->column->remove(target);\n\n    if (!DATA->column) {\n        // column got removed, let's ensure we don't leave any cringe extra space\n        const auto   USABLE         = usableArea();\n        const bool   isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal();\n        const double usablePrimary  = isPrimaryHoriz ? USABLE.w : USABLE.h;\n        const double newOffset      = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - usablePrimary, 1.0));\n        m_scrollingData->controller->setOffset(newOffset);\n    }\n\n    m_scrollingData->recalculate();\n}\n\nvoid CScrollingAlgorithm::resizeTarget(const Vector2D& delta, SP<ITarget> target, eRectCorner corner) {\n    if (!validMapped(target->window()))\n        return;\n\n    const auto DATA = dataFor(target);\n\n    if (!DATA) {\n        const auto PWINDOW   = target->window();\n        *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + delta)\n                                   .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}),\n                                          PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}));\n        PWINDOW->updateWindowDecos();\n        return;\n    }\n\n    if (!DATA->column || !DATA->column->scrollingData)\n        return;\n\n    static const auto PFSONONE = CConfigValue<Hyprlang::INT>(\"scrolling:fullscreen_on_one_column\");\n\n    const auto        ADJUSTED_DELTA = m_scrollingData->controller->isPrimaryHorizontal() ? delta : Vector2D{delta.y, delta.x};\n    const auto        USABLE         = usableArea();\n    const auto        DELTA_AS_PERC  = ADJUSTED_DELTA / USABLE.size();\n    Vector2D          modDelta       = ADJUSTED_DELTA;\n\n    const auto        CURR_COLUMN = DATA->column.lock();\n    const int64_t     COL_IDX     = m_scrollingData->idx(CURR_COLUMN);\n\n    if (COL_IDX < 0)\n        return;\n\n    const double currentStart = m_scrollingData->controller->calculateStripStart(COL_IDX, USABLE, *PFSONONE);\n    const double currentSize  = m_scrollingData->controller->calculateStripSize(COL_IDX, USABLE, *PFSONONE);\n    const double currentEnd   = currentStart + currentSize;\n\n    const double cameraOffset   = m_scrollingData->controller->getOffset();\n    const bool   isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal();\n    const double usablePrimary  = isPrimaryHoriz ? USABLE.w : USABLE.h;\n\n    const double onScreenStart = currentStart - cameraOffset;\n    const double onScreenEnd   = currentEnd - cameraOffset;\n\n    // set the offset because we'll prevent centering during a drag\n    m_scrollingData->controller->setOffset(cameraOffset);\n\n    const bool RESIZING_LEFT = isPrimaryHoriz ? corner == CORNER_BOTTOMLEFT || corner == CORNER_TOPLEFT : corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT;\n\n    if (RESIZING_LEFT) {\n        // resize from left edge (inner edge) - grow/shrink column width and adjust offset to keep RIGHT edge stationary\n        const float oldWidth       = CURR_COLUMN->getColumnWidth();\n        const float requestedDelta = -(float)DELTA_AS_PERC.x; // negative delta means grow when dragging left\n        float       actualDelta    = requestedDelta;\n\n        // clamp delta so we don't shrink below MIN or grow above MAX\n        const float newWidthUnclamped = oldWidth + actualDelta;\n        const float newWidthClamped   = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);\n        actualDelta                   = newWidthClamped - oldWidth;\n\n        if (actualDelta * usablePrimary > onScreenStart)\n            actualDelta = onScreenStart / usablePrimary;\n\n        if (actualDelta != 0.F) {\n            CURR_COLUMN->setColumnWidth(oldWidth + actualDelta);\n            // adjust camera offset so the RIGHT edge stays stationary on screen\n            // when column grows (actualDelta > 0), we need to increase offset by the same amount\n            m_scrollingData->controller->adjustOffset(actualDelta * usablePrimary);\n        }\n\n    } else {\n        // resize from right edge (outer edge) - adjust column width only, keep left edge fixed\n        const float oldWidth       = CURR_COLUMN->getColumnWidth();\n        const float requestedDelta = (float)DELTA_AS_PERC.x;\n        float       actualDelta    = requestedDelta;\n\n        // clamp delta so we don't shrink below MIN or grow above MAX\n        const float newWidthUnclamped = oldWidth + actualDelta;\n        const float newWidthClamped   = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);\n        actualDelta                   = newWidthClamped - oldWidth;\n\n        // also clamp so right edge doesn't go past right viewport boundary\n        if (onScreenEnd + (actualDelta * usablePrimary) > usablePrimary)\n            actualDelta = (usablePrimary - onScreenEnd) / usablePrimary;\n\n        if (actualDelta != 0.F)\n            CURR_COLUMN->setColumnWidth(oldWidth + actualDelta);\n    }\n\n    if (DATA->column->targetDatas.size() > 1) {\n        const auto& CURR_TD = DATA;\n        const auto  NEXT_TD = DATA->column->next(DATA);\n        const auto  PREV_TD = DATA->column->prev(DATA);\n        if (corner == CORNER_NONE) {\n            if (!PREV_TD)\n                corner = CORNER_BOTTOMRIGHT;\n            else {\n                corner = CORNER_TOPRIGHT;\n                modDelta.y *= -1.0f;\n            }\n        }\n\n        switch (corner) {\n            case CORNER_BOTTOMLEFT:\n            case CORNER_BOTTOMRIGHT: {\n                if (!NEXT_TD)\n                    break;\n\n                float nextSize = CURR_COLUMN->getTargetSize(NEXT_TD);\n                float currSize = CURR_COLUMN->getTargetSize(CURR_TD);\n\n                if (nextSize <= MIN_ROW_HEIGHT && delta.y >= 0)\n                    break;\n\n                float adjust = std::clamp((float)(delta.y / USABLE.h), (-currSize + MIN_ROW_HEIGHT), (nextSize - MIN_ROW_HEIGHT));\n\n                CURR_COLUMN->setTargetSize(NEXT_TD, std::clamp(nextSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));\n                CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));\n                break;\n            }\n            case CORNER_TOPLEFT:\n            case CORNER_TOPRIGHT: {\n                if (!PREV_TD)\n                    break;\n\n                float prevSize = CURR_COLUMN->getTargetSize(PREV_TD);\n                float currSize = CURR_COLUMN->getTargetSize(CURR_TD);\n\n                if ((prevSize <= MIN_ROW_HEIGHT && modDelta.y <= 0) || (currSize <= MIN_ROW_HEIGHT && delta.y >= 0))\n                    break;\n\n                float adjust = std::clamp((float)(modDelta.y / USABLE.h), -(prevSize - MIN_ROW_HEIGHT), (currSize - MIN_ROW_HEIGHT));\n\n                CURR_COLUMN->setTargetSize(PREV_TD, std::clamp(prevSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));\n                CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT));\n                break;\n            }\n\n            default: break;\n        }\n    }\n\n    m_scrollingData->recalculate(true);\n}\n\nvoid CScrollingAlgorithm::recalculate() {\n    // guard against recalculation during transitional monitor states\n    // (e.g. monitor reconnecting after suspend where workspace/monitor may not be ready)\n    if (!m_parent || !m_parent->space() || !m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor)\n        return;\n\n    if (Desktop::focusState()->window()) {\n        const auto TARGET = Desktop::focusState()->window()->layoutTarget();\n\n        const auto TARGETDATA = dataFor(TARGET);\n\n        if (TARGETDATA && !m_scrollingData->visible(TARGETDATA->column.lock(), true))\n            focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_KB);\n    }\n\n    m_scrollingData->recalculate();\n}\n\nSP<SScrollingTargetData> CScrollingAlgorithm::closestNode(const Vector2D& posGlobglobgabgalab) {\n    SP<SScrollingTargetData> res         = nullptr;\n    double                   distClosest = -1;\n    for (auto& c : m_scrollingData->columns) {\n        for (auto& n : c->targetDatas) {\n            if (n->target && Desktop::View::validMapped(n->target->window())) {\n                auto distAnother = vecToRectDistanceSquared(posGlobglobgabgalab, n->layoutBox.pos(), n->layoutBox.pos() + n->layoutBox.size());\n                if (!res || distAnother < distClosest) {\n                    res         = n;\n                    distClosest = distAnother;\n                }\n            }\n        }\n    }\n    return res;\n}\n\nSP<ITarget> CScrollingAlgorithm::getNextCandidate(SP<ITarget> old) {\n    const auto CENTER = old->position().middle();\n\n    const auto NODE = closestNode(CENTER);\n\n    if (!NODE)\n        return nullptr;\n\n    return NODE->target.lock();\n}\n\nvoid CScrollingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {\n    auto nodeA = dataFor(a);\n    auto nodeB = dataFor(b);\n\n    if (nodeA)\n        nodeA->target = b;\n    if (nodeB)\n        nodeB->target = a;\n\n    m_scrollingData->recalculate();\n}\n\nvoid CScrollingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    moveTargetTo(t, dir, silent);\n}\n\nvoid CScrollingAlgorithm::moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>(\"binds:window_direction_monitor_fallback\");\n\n    const auto  DATA = dataFor(t);\n\n    if (!DATA)\n        return;\n\n    const auto CURRENT_COL = DATA->column.lock();\n    const auto current_idx = m_scrollingData->idx(CURRENT_COL);\n\n    auto       rotateDir = [this](Math::eDirection dir) -> Math::eDirection {\n        switch (m_scrollingData->controller->getDirection()) {\n            case SCROLL_DIR_RIGHT: return dir;\n            case SCROLL_DIR_LEFT: {\n                if (dir == Math::DIRECTION_LEFT)\n                    return Math::DIRECTION_RIGHT;\n                if (dir == Math::DIRECTION_RIGHT)\n                    return Math::DIRECTION_LEFT;\n                return dir;\n            }\n            case SCROLL_DIR_UP: {\n                switch (dir) {\n                    case Math::DIRECTION_UP: return Math::DIRECTION_RIGHT;\n                    case Math::DIRECTION_DOWN: return Math::DIRECTION_LEFT;\n                    case Math::DIRECTION_LEFT: return Math::DIRECTION_UP;\n                    case Math::DIRECTION_RIGHT: return Math::DIRECTION_DOWN;\n                    default: break;\n                }\n\n                return dir;\n            }\n            case SCROLL_DIR_DOWN: {\n                switch (dir) {\n                    case Math::DIRECTION_UP: return Math::DIRECTION_LEFT;\n                    case Math::DIRECTION_DOWN: return Math::DIRECTION_RIGHT;\n                    case Math::DIRECTION_LEFT: return Math::DIRECTION_UP;\n                    case Math::DIRECTION_RIGHT: return Math::DIRECTION_DOWN;\n                    default: break;\n                }\n\n                return dir;\n            }\n            default: break;\n        }\n\n        return dir;\n    };\n\n    const auto ROTATED_DIR = rotateDir(dir);\n\n    auto       commenceDir = [&]() -> bool {\n        if (ROTATED_DIR == Math::DIRECTION_LEFT) {\n            const auto COL = m_scrollingData->prev(DATA->column.lock());\n\n            // ignore moves to the origin if we are alone\n            if (!COL && current_idx == 0 && DATA->column->targetDatas.size() == 1)\n                return false;\n\n            DATA->column->remove(t);\n\n            if (!COL) {\n                const auto NEWCOL = m_scrollingData->add(-1);\n                NEWCOL->add(DATA);\n                m_scrollingData->centerOrFitCol(NEWCOL);\n            } else {\n                if (COL->targetDatas.size() > 0)\n                    COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));\n                else\n                    COL->add(DATA);\n                m_scrollingData->centerOrFitCol(COL);\n            }\n\n            return true;\n        } else if (ROTATED_DIR == Math::DIRECTION_RIGHT) {\n            const auto COL = m_scrollingData->next(DATA->column.lock());\n\n            // ignore move to the right when there is no next column and we're alone\n            if (!COL && current_idx == (int64_t)m_scrollingData->columns.size() - 1 && DATA->column->targetDatas.size() == 1)\n                return false;\n\n            DATA->column->remove(t);\n\n            if (!COL) {\n                // make a new one\n                const auto NEWCOL = m_scrollingData->add();\n                NEWCOL->add(DATA);\n                m_scrollingData->centerOrFitCol(NEWCOL);\n            } else {\n                if (COL->targetDatas.size() > 0)\n                    COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));\n                else\n                    COL->add(DATA);\n                m_scrollingData->centerOrFitCol(COL);\n            }\n\n            return true;\n        } else if (ROTATED_DIR == Math::DIRECTION_UP)\n            return DATA->column->up(DATA);\n        else if (ROTATED_DIR == Math::DIRECTION_DOWN)\n            return DATA->column->down(DATA);\n\n        return false;\n    };\n\n    if (!commenceDir()) {\n        // dir wasn't commenced, move to a workspace if possible\n        // with the original dir\n\n        if (!*PMONITORFALLBACK)\n            return; // noop\n\n        const auto MONINDIR = g_pCompositor->getMonitorInDirection(m_parent->space()->workspace()->m_monitor.lock(), dir);\n        if (MONINDIR && MONINDIR != m_parent->space()->workspace()->m_monitor && MONINDIR->m_activeWorkspace) {\n            t->assignToSpace(MONINDIR->m_activeWorkspace->m_space, focalPointForDir(t, dir));\n\n            m_scrollingData->recalculate();\n\n            return;\n        }\n    }\n\n    m_scrollingData->recalculate();\n    focusTargetUpdate(t);\n}\n\nstd::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::string_view& sv) {\n    auto centerOrFit = [this](const SP<SColumnData> COL) -> void {\n        static const auto PFITMETHOD = CConfigValue<Hyprlang::INT>(\"scrolling:focus_fit_method\");\n        if (*PFITMETHOD == 1)\n            m_scrollingData->fitCol(COL);\n        else\n            m_scrollingData->centerCol(COL);\n    };\n\n    const auto ARGS = CVarList(std::string{sv}, 0, ' ');\n    if (ARGS[0] == \"move\") {\n        if (ARGS[1] == \"+col\" || ARGS[1] == \"col\") {\n            const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);\n            if (!TDATA)\n                return std::unexpected(\"no window\");\n\n            const auto COL = m_scrollingData->next(TDATA->column.lock());\n            if (!COL) {\n                // move to max\n                double maxOffset = m_scrollingData->maxWidth();\n                m_scrollingData->controller->setOffset(maxOffset);\n                m_scrollingData->recalculate();\n                focusTargetUpdate(nullptr);\n                return {};\n            }\n\n            centerOrFit(COL);\n            m_scrollingData->recalculate();\n\n            focusTargetUpdate(COL->targetDatas.front()->target.lock());\n            if (COL->targetDatas.front()->target->window())\n                g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle());\n\n            return {};\n        } else if (ARGS[1] == \"-col\") {\n            const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);\n            if (!TDATA) {\n                if (m_scrollingData->columns.size() > 0) {\n                    m_scrollingData->centerCol(m_scrollingData->columns.back());\n                    m_scrollingData->recalculate();\n                    focusTargetUpdate((m_scrollingData->columns.back()->targetDatas.back())->target.lock());\n                    if (m_scrollingData->columns.back()->targetDatas.back()->target->window())\n                        g_pCompositor->warpCursorTo((m_scrollingData->columns.back()->targetDatas.back())->target->window()->middle());\n                }\n\n                return {};\n            }\n\n            const auto COL = m_scrollingData->prev(TDATA->column.lock());\n            if (!COL)\n                return {};\n\n            centerOrFit(COL);\n            m_scrollingData->recalculate();\n\n            focusTargetUpdate(COL->targetDatas.back()->target.lock());\n            if (COL->targetDatas.front()->target->window())\n                g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle());\n\n            return {};\n        }\n\n        const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0);\n\n        if (!PLUSMINUS.has_value())\n            return std::unexpected(\"failed to parse offset\");\n\n        m_scrollingData->controller->adjustOffset(-(*PLUSMINUS));\n        m_scrollingData->recalculate();\n\n        const auto ATCENTER = m_scrollingData->atCenter();\n\n        focusTargetUpdate(ATCENTER ? (*ATCENTER->targetDatas.begin())->target.lock() : nullptr);\n    } else if (ARGS[0] == \"colresize\") {\n        const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);\n\n        if (!TDATA)\n            return {};\n\n        if (ARGS[1] == \"all\") {\n            float abs = 0;\n            try {\n                abs = std::stof(ARGS[2]);\n            } catch (...) { return {}; }\n\n            for (const auto& c : m_scrollingData->columns) {\n                c->setColumnWidth(abs);\n            }\n\n            m_scrollingData->recalculate();\n            return {};\n        }\n\n        CScopeGuard x([this, TDATA] {\n            auto col = TDATA->column.lock();\n            if (col) {\n                col->setColumnWidth(std::clamp(col->getColumnWidth(), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH));\n                m_scrollingData->centerOrFitCol(col);\n            }\n            m_scrollingData->recalculate();\n        });\n\n        if (ARGS[1][0] == '+' || ARGS[1][0] == '-') {\n            if (ARGS[1] == \"+conf\") {\n                auto col = TDATA->column.lock();\n                if (col) {\n                    for (size_t i = 0; i < m_config.configuredWidths.size(); ++i) {\n                        if (m_config.configuredWidths[i] > col->getColumnWidth()) {\n                            col->setColumnWidth(m_config.configuredWidths[i]);\n                            break;\n                        }\n\n                        if (i == m_config.configuredWidths.size() - 1)\n                            col->setColumnWidth(m_config.configuredWidths[0]);\n                    }\n                }\n\n                return {};\n            } else if (ARGS[1] == \"-conf\") {\n                auto col = TDATA->column.lock();\n                if (col) {\n                    for (size_t i = m_config.configuredWidths.size() - 1;; --i) {\n                        if (m_config.configuredWidths[i] < col->getColumnWidth()) {\n                            col->setColumnWidth(m_config.configuredWidths[i]);\n                            break;\n                        }\n\n                        if (i == 0) {\n                            col->setColumnWidth(m_config.configuredWidths.back());\n                            break;\n                        }\n                    }\n                }\n\n                return {};\n            }\n\n            const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0);\n\n            if (!PLUSMINUS.has_value())\n                return {};\n\n            auto col = TDATA->column.lock();\n            if (col)\n                col->setColumnWidth(col->getColumnWidth() + *PLUSMINUS);\n        } else {\n            float abs = 0;\n            try {\n                abs = std::stof(ARGS[1]);\n            } catch (...) { return {}; }\n\n            auto col = TDATA->column.lock();\n            if (col)\n                col->setColumnWidth(abs);\n        }\n    } else if (ARGS[0] == \"fit\") {\n        const auto PWINDOW = Desktop::focusState()->window();\n\n        if (!PWINDOW)\n            return std::unexpected(\"no focused window\");\n\n        const auto WDATA = dataFor(PWINDOW->layoutTarget());\n\n        if (!WDATA || m_scrollingData->columns.size() == 0)\n            return std::unexpected(\"can't fit: no window or columns\");\n\n        if (ARGS[1] == \"active\") {\n            // fit the current column to 1.F\n            const auto USABLE = usableArea();\n\n            WDATA->column->setColumnWidth(1.F);\n\n            double off = 0.F;\n            for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) {\n                if (m_scrollingData->columns[i]->has(PWINDOW->layoutTarget()))\n                    break;\n\n                off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth();\n            }\n\n            m_scrollingData->controller->setOffset(off);\n            m_scrollingData->recalculate();\n        } else if (ARGS[1] == \"all\") {\n            // fit all columns on screen\n            const size_t LEN = m_scrollingData->columns.size();\n            for (const auto& c : m_scrollingData->columns) {\n                c->setColumnWidth(1.F / (float)LEN);\n            }\n\n            m_scrollingData->controller->setOffset(0);\n            m_scrollingData->recalculate();\n        } else if (ARGS[1] == \"toend\") {\n            // fit all columns on screen that start from the current and end on the last\n            bool   begun   = false;\n            size_t foundAt = 0;\n            for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) {\n                if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget()))\n                    continue;\n\n                if (!begun) {\n                    begun   = true;\n                    foundAt = i;\n                }\n\n                m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(m_scrollingData->columns.size() - foundAt));\n            }\n\n            if (!begun)\n                return std::unexpected(\"couldn't find beginning\");\n\n            const auto USABLE = usableArea();\n\n            double     off = 0;\n            for (size_t i = 0; i < foundAt; ++i) {\n                off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth();\n            }\n\n            m_scrollingData->controller->setOffset(off);\n            m_scrollingData->recalculate();\n        } else if (ARGS[1] == \"tobeg\") {\n            // fit all columns on screen that start from the current and end on the last\n            bool   begun   = false;\n            size_t foundAt = 0;\n            for (int64_t i = (int64_t)m_scrollingData->columns.size() - 1; i >= 0; --i) {\n                if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget()))\n                    continue;\n\n                if (!begun) {\n                    begun   = true;\n                    foundAt = i;\n                }\n\n                m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(foundAt + 1));\n            }\n\n            if (!begun)\n                return {};\n\n            m_scrollingData->controller->setOffset(0);\n            m_scrollingData->recalculate();\n        } else if (ARGS[1] == \"visible\") {\n            // fit all columns on screen that start from the current and end on the last\n\n            bool                         begun   = false;\n            size_t                       foundAt = 0;\n            std::vector<SP<SColumnData>> visible;\n            for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) {\n                if (!begun && !m_scrollingData->visible(m_scrollingData->columns[i]))\n                    continue;\n\n                if (!begun) {\n                    begun   = true;\n                    foundAt = i;\n                }\n\n                if (!m_scrollingData->visible(m_scrollingData->columns[i]))\n                    break;\n\n                visible.emplace_back(m_scrollingData->columns[i]);\n            }\n\n            if (!begun)\n                return {};\n\n            double off = 0;\n\n            if (foundAt != 0) {\n                const auto USABLE = usableArea();\n\n                for (size_t i = 0; i < foundAt; ++i) {\n                    off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth();\n                }\n            }\n\n            for (const auto& v : visible) {\n                v->setColumnWidth(1.F / (float)visible.size());\n            }\n\n            m_scrollingData->controller->setOffset(off);\n            m_scrollingData->recalculate();\n        }\n    } else if (ARGS[0] == \"focus\") {\n        const auto        TDATA          = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);\n        static const auto PNOFALLBACK    = CConfigValue<Hyprlang::INT>(\"general:no_focus_fallback\");\n        static const auto PCONFWRAPFOCUS = CConfigValue<Hyprlang::INT>(\"scrolling:wrap_focus\");\n\n        if (!TDATA || ARGS[1].empty())\n            return std::unexpected(\"no window to focus\");\n\n        // Determine if we're in vertical scroll mode (strips are horizontal)\n        const bool isVerticalScroll = (getDynamicDirection() == SCROLL_DIR_DOWN || getDynamicDirection() == SCROLL_DIR_UP);\n\n        // Map direction keys based on scroll mode:\n        // Horizontal scroll (RIGHT/LEFT): u/d move within strip, l/r move between strips\n        // Vertical scroll (DOWN/UP): l/r move within strip, u/d move between strips\n        char dirChar = ARGS[1][0];\n\n        // Convert to semantic directions\n        bool isPrevInStrip = (!isVerticalScroll && (dirChar == 'u' || dirChar == 't')) || (isVerticalScroll && dirChar == 'l');\n        bool isNextInStrip = (!isVerticalScroll && (dirChar == 'b' || dirChar == 'd')) || (isVerticalScroll && dirChar == 'r');\n        bool isPrevStrip   = (!isVerticalScroll && dirChar == 'l') || (isVerticalScroll && (dirChar == 'u' || dirChar == 't'));\n        bool isNextStrip   = (!isVerticalScroll && dirChar == 'r') || (isVerticalScroll && (dirChar == 'b' || dirChar == 'd'));\n\n        if (isPrevInStrip) {\n            // Move to previous target within current strip\n            auto PREV = TDATA->column->prev(TDATA);\n            if (!PREV) {\n                if (!*PNOFALLBACK)\n                    PREV = TDATA->column->targetDatas.back();\n                else\n                    return std::unexpected(\"fallback disabled (no target)\");\n            }\n\n            focusTargetUpdate(PREV->target.lock());\n            if (PREV->target->window())\n                g_pCompositor->warpCursorTo(PREV->target->window()->middle());\n        } else if (isNextInStrip) {\n            // Move to next target within current strip\n            auto NEXT = TDATA->column->next(TDATA);\n            if (!NEXT) {\n                if (!*PNOFALLBACK)\n                    NEXT = TDATA->column->targetDatas.front();\n                else\n                    return std::unexpected(\"fallback disabled (no target)\");\n            }\n\n            focusTargetUpdate(NEXT->target.lock());\n            if (NEXT->target->window())\n                g_pCompositor->warpCursorTo(NEXT->target->window()->middle());\n        } else if (isPrevStrip) {\n            // Move to previous strip\n            auto PREV = m_scrollingData->prev(TDATA->column.lock());\n            if (!PREV) {\n                if (*PNOFALLBACK) {\n                    centerOrFit(TDATA->column.lock());\n                    m_scrollingData->recalculate();\n                    if (TDATA->target->window())\n                        g_pCompositor->warpCursorTo(TDATA->target->window()->middle());\n                    return {};\n                } else\n                    PREV = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.back() : m_scrollingData->columns.front();\n            }\n\n            auto pTargetData = findBestNeighbor(TDATA, PREV);\n            if (pTargetData) {\n                focusTargetUpdate(pTargetData->target.lock());\n                centerOrFit(PREV);\n                m_scrollingData->recalculate();\n                if (pTargetData->target->window())\n                    g_pCompositor->warpCursorTo(pTargetData->target->window()->middle());\n            }\n        } else if (isNextStrip) {\n            // Move to next strip\n            auto NEXT = m_scrollingData->next(TDATA->column.lock());\n            if (!NEXT) {\n                if (*PNOFALLBACK) {\n                    centerOrFit(TDATA->column.lock());\n                    m_scrollingData->recalculate();\n                    if (TDATA->target->window())\n                        g_pCompositor->warpCursorTo(TDATA->target->window()->middle());\n                    return {};\n                } else\n                    NEXT = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.front() : m_scrollingData->columns.back();\n            }\n\n            auto pTargetData = findBestNeighbor(TDATA, NEXT);\n            if (pTargetData) {\n                focusTargetUpdate(pTargetData->target.lock());\n                centerOrFit(NEXT);\n                m_scrollingData->recalculate();\n                if (pTargetData->target->window())\n                    g_pCompositor->warpCursorTo(pTargetData->target->window()->middle());\n            }\n        }\n    } else if (ARGS[0] == \"promote\") {\n        const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);\n\n        if (!TDATA)\n            return std::unexpected(\"no window focused\");\n\n        auto idx = m_scrollingData->idx(TDATA->column.lock());\n        auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx);\n\n        TDATA->column->remove(TDATA->target.lock());\n\n        col->add(TDATA);\n\n        m_scrollingData->recalculate();\n    } else if (ARGS[0] == \"swapcol\") {\n        static const auto PCONFWRAPSWAPCOL = CConfigValue<Hyprlang::INT>(\"scrolling:wrap_swapcol\");\n\n        if (ARGS.size() < 2)\n            return std::unexpected(\"not enough args\");\n\n        const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);\n        if (!TDATA)\n            return std::unexpected(\"no window\");\n\n        const auto CURRENT_COL = TDATA->column.lock();\n        if (!CURRENT_COL)\n            return std::unexpected(\"no current col\");\n\n        if (m_scrollingData->columns.size() < 2)\n            return std::unexpected(\"not enough columns to swap\");\n\n        const int64_t currentIdx = m_scrollingData->idx(CURRENT_COL);\n        const size_t  colCount   = m_scrollingData->columns.size();\n\n        if (currentIdx == -1)\n            return std::unexpected(\"no current column\");\n\n        const std::string& direction = ARGS[1];\n        int64_t            targetIdx = -1;\n\n        // wrap around swaps\n        if (direction == \"l\")\n            if (*PCONFWRAPSWAPCOL == 1)\n                targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1);\n            else\n                targetIdx = (currentIdx == 0) ? 0 : (currentIdx - 1);\n        else if (direction == \"r\")\n            if (*PCONFWRAPSWAPCOL == 1)\n                targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1);\n            else\n                targetIdx = (currentIdx == (int64_t)colCount - 1) ? (colCount - 1) : (currentIdx + 1);\n        else\n            return std::unexpected(\"no target (invalid direction?)\");\n        ;\n\n        std::swap(m_scrollingData->columns.at(currentIdx), m_scrollingData->columns.at(targetIdx));\n\n        m_scrollingData->controller->swapStrips(currentIdx, targetIdx);\n\n        m_scrollingData->centerOrFitCol(CURRENT_COL);\n        m_scrollingData->recalculate();\n    } else\n        return std::unexpected(\"no such layoutmsg for scrolling\");\n\n    return {};\n}\n\nstd::optional<Vector2D> CScrollingAlgorithm::predictSizeForNewTarget() {\n    return std::nullopt;\n}\n\nvoid CScrollingAlgorithm::focusTargetUpdate(SP<ITarget> target) {\n    if (!target || !validMapped(target->window())) {\n        Desktop::focusState()->fullWindowFocus(nullptr, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);\n        return;\n    }\n    Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);\n    const auto TARGETDATA = dataFor(target);\n    if (TARGETDATA) {\n        if (auto col = TARGETDATA->column.lock())\n            col->lastFocusedTarget = TARGETDATA;\n    }\n}\n\nSP<SScrollingTargetData> CScrollingAlgorithm::findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol) {\n    if (!pCurrent || !pTargetCol || pTargetCol->targetDatas.empty())\n        return nullptr;\n\n    const double                          currentTop    = pCurrent->layoutBox.y;\n    const double                          currentBottom = pCurrent->layoutBox.y + pCurrent->layoutBox.h;\n    std::vector<SP<SScrollingTargetData>> overlappingTargets;\n    for (const auto& candidate : pTargetCol->targetDatas) {\n        const double candidateTop    = candidate->layoutBox.y;\n        const double candidateBottom = candidate->layoutBox.y + candidate->layoutBox.h;\n        const bool   overlaps        = (candidateTop < currentBottom) && (candidateBottom > currentTop);\n\n        if (overlaps)\n            overlappingTargets.emplace_back(candidate);\n    }\n    if (!overlappingTargets.empty()) {\n        auto lastFocused = pTargetCol->lastFocusedTarget.lock();\n\n        if (lastFocused) {\n            auto it = std::ranges::find(overlappingTargets, lastFocused);\n            if (it != overlappingTargets.end())\n                return lastFocused;\n        }\n\n        auto topmost = std::ranges::min_element(overlappingTargets, std::less<>{}, [](const SP<SScrollingTargetData>& t) { return t->layoutBox.y; });\n        return *topmost;\n    }\n    if (!pTargetCol->targetDatas.empty())\n        return pTargetCol->targetDatas.front();\n    return nullptr;\n}\n\nSP<SScrollingTargetData> CScrollingAlgorithm::dataFor(SP<ITarget> t) {\n    if (!t)\n        return nullptr;\n\n    for (const auto& c : m_scrollingData->columns) {\n        for (const auto& d : c->targetDatas) {\n            if (d->target == t)\n                return d;\n        }\n    }\n\n    return nullptr;\n}\n\neScrollDirection CScrollingAlgorithm::getDynamicDirection() {\n    const auto  WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace());\n    std::string directionString;\n    if (WORKSPACERULE.layoutopts.contains(\"direction\"))\n        directionString = WORKSPACERULE.layoutopts.at(\"direction\");\n\n    static const auto PCONFDIRECTION  = CConfigValue<Hyprlang::STRING>(\"scrolling:direction\");\n    std::string       configDirection = *PCONFDIRECTION;\n\n    // Workspace rule overrides global config\n    if (!directionString.empty())\n        configDirection = directionString;\n\n    // Parse direction string\n    if (configDirection == \"left\")\n        return SCROLL_DIR_LEFT;\n    else if (configDirection == \"down\")\n        return SCROLL_DIR_DOWN;\n    else if (configDirection == \"up\")\n        return SCROLL_DIR_UP;\n    else\n        return SCROLL_DIR_RIGHT; // default\n}\n\nCBox CScrollingAlgorithm::usableArea() {\n    if (!m_parent || !m_parent->space())\n        return {};\n\n    CBox box = m_parent->space()->workArea();\n\n    // doesn't matter, this happens when this algo is about to be destroyed\n    if (!m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor)\n        return box;\n\n    box.translate(-m_parent->space()->workspace()->m_monitor->m_position);\n\n    // ensure dimensions are never zero or negative, which can happen during\n    // monitor transitions (e.g. reconnection after suspend with stale reserved areas)\n    box.w = std::max(box.w, 1.0);\n    box.h = std::max(box.h, 1.0);\n\n    return box;\n}\n\nfloat CScrollingAlgorithm::defaultColumnWidth() {\n    static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>(\"scrolling:column_width\");\n    return std::clamp(*PCOLWIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);\n}\n"
  },
  {
    "path": "src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp",
    "content": "#pragma once\n\n#include \"../../TiledAlgorithm.hpp\"\n#include \"../../../../helpers/math/Direction.hpp\"\n#include \"ScrollTapeController.hpp\"\n#include \"../../../../helpers/signal/Signal.hpp\"\n\n#include <vector>\n\nnamespace Layout::Tiled {\n    class CScrollingAlgorithm;\n    struct SColumnData;\n    struct SScrollingData;\n\n    struct SScrollingTargetData {\n        SScrollingTargetData(SP<ITarget> t, SP<SColumnData> col) : target(t), column(col) {\n            ;\n        }\n\n        WP<ITarget>     target;\n        WP<SColumnData> column;\n        bool            ignoreFullscreenChecks = false;\n\n        CBox            layoutBox;\n    };\n\n    struct SColumnData {\n        SColumnData(SP<SScrollingData> data) : scrollingData(data) {\n            ;\n        }\n\n        void   add(SP<ITarget> t);\n        void   add(SP<ITarget> t, int after);\n        void   add(SP<SScrollingTargetData> w);\n        void   add(SP<SScrollingTargetData> w, int after);\n        void   remove(SP<ITarget> t);\n        bool   has(SP<ITarget> t);\n        size_t idx(SP<ITarget> t);\n\n        // index of lowest target that is above y.\n        size_t                                idxForHeight(float y);\n\n        bool                                  up(SP<SScrollingTargetData> w);\n        bool                                  down(SP<SScrollingTargetData> w);\n\n        SP<SScrollingTargetData>              next(SP<SScrollingTargetData> w);\n        SP<SScrollingTargetData>              prev(SP<SScrollingTargetData> w);\n\n        std::vector<SP<SScrollingTargetData>> targetDatas;\n        WP<SScrollingData>                    scrollingData;\n        WP<SScrollingTargetData>              lastFocusedTarget;\n\n        WP<SColumnData>                       self;\n\n        // Helper methods to access controller-managed data\n        float getColumnWidth() const;\n        void  setColumnWidth(float width);\n        float getTargetSize(size_t idx) const;\n        void  setTargetSize(size_t idx, float size);\n        float getTargetSize(SP<SScrollingTargetData> target) const;\n        void  setTargetSize(SP<SScrollingTargetData> target, float size);\n    };\n\n    struct SScrollingData {\n        SScrollingData(CScrollingAlgorithm* algo);\n\n        std::vector<SP<SColumnData>> columns;\n\n        UP<CScrollTapeController>    controller;\n\n        SP<SColumnData>              add(std::optional<float> width = std::nullopt);\n        SP<SColumnData>              add(int after, std::optional<float> width = std::nullopt);\n        int64_t                      idx(SP<SColumnData> c);\n        void                         remove(SP<SColumnData> c);\n        double                       maxWidth();\n        SP<SColumnData>              next(SP<SColumnData> c);\n        SP<SColumnData>              prev(SP<SColumnData> c);\n        SP<SColumnData>              atCenter();\n\n        bool                         visible(SP<SColumnData> c, bool full = false);\n        void                         centerCol(SP<SColumnData> c);\n        void                         fitCol(SP<SColumnData> c);\n        void                         centerOrFitCol(SP<SColumnData> c);\n\n        void                         recalculate(bool forceInstant = false);\n\n        CScrollingAlgorithm*         algorithm = nullptr;\n        WP<SScrollingData>           self;\n        std::optional<double>        lockedCameraOffset;\n    };\n\n    class CScrollingAlgorithm : public ITiledAlgorithm {\n      public:\n        CScrollingAlgorithm();\n        virtual ~CScrollingAlgorithm();\n\n        virtual void                             newTarget(SP<ITarget> target);\n        virtual void                             movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual void                             removeTarget(SP<ITarget> target);\n\n        virtual void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        virtual void                             recalculate();\n\n        virtual SP<ITarget>                      getNextCandidate(SP<ITarget> old);\n\n        virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n        virtual std::optional<Vector2D>          predictSizeForNewTarget();\n\n        virtual void                             swapTargets(SP<ITarget> a, SP<ITarget> b);\n        virtual void                             moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n        CBox                                     usableArea();\n\n        enum eInputMode : uint8_t {\n            INPUT_MODE_SOFT = 0,\n            INPUT_MODE_CLICK,\n            INPUT_MODE_KB\n        };\n\n      private:\n        SP<SScrollingData>  m_scrollingData;\n\n        CHyprSignalListener m_configCallback;\n        CHyprSignalListener m_focusCallback;\n        CHyprSignalListener m_mouseButtonCallback;\n\n        struct {\n            std::vector<float> configuredWidths;\n        } m_config;\n\n        eScrollDirection         getDynamicDirection();\n\n        SP<SScrollingTargetData> findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol);\n        SP<SScrollingTargetData> dataFor(SP<ITarget> t);\n        SP<SScrollingTargetData> closestNode(const Vector2D& posGlobglobgabgalab);\n\n        void                     focusTargetUpdate(SP<ITarget> target);\n        void                     moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);\n        void                     focusOnInput(SP<ITarget> target, eInputMode input);\n\n        float                    defaultColumnWidth();\n\n        friend struct SScrollingData;\n    };\n};\n"
  },
  {
    "path": "src/layout/space/Space.cpp",
    "content": "#include \"Space.hpp\"\n\n#include \"../target/Target.hpp\"\n#include \"../algorithm/Algorithm.hpp\"\n\n#include \"../../debug/log/Logger.hpp\"\n#include \"../../desktop/Workspace.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nusing namespace Layout;\n\nSP<CSpace> CSpace::create(PHLWORKSPACE w) {\n    auto space    = SP<CSpace>(new CSpace(w));\n    space->m_self = space;\n    return space;\n}\n\nCSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) {\n    recheckWorkArea();\n\n    // NOLINTNEXTLINE\n    m_geomUpdateCallback = Event::bus()->m_events.monitor.layoutChanged.listen([this] {\n        // During monitor disconnect/reconnect (e.g. sleep/wake), some workspaces\n        // may have stale or null monitors. Guard against that to avoid crashing\n        // when recalculating layout for workspaces mid-migration.\n        if (!m_parent || !m_parent->m_monitor)\n            return;\n\n        recheckWorkArea();\n\n        if (m_algorithm)\n            m_algorithm->recalculate();\n    });\n}\n\nvoid CSpace::add(SP<ITarget> t) {\n    m_targets.emplace_back(t);\n\n    recheckWorkArea();\n\n    if (m_algorithm)\n        m_algorithm->addTarget(t);\n\n    m_parent->updateWindows();\n}\n\nvoid CSpace::move(SP<ITarget> t, std::optional<Vector2D> focalPoint) {\n    m_targets.emplace_back(t);\n\n    recheckWorkArea();\n\n    if (m_algorithm)\n        m_algorithm->moveTarget(t, focalPoint);\n\n    m_parent->updateWindows();\n}\n\nvoid CSpace::remove(SP<ITarget> t) {\n    std::erase_if(m_targets, [&t](const auto& e) { return !e || e == t; });\n\n    recheckWorkArea();\n\n    if (m_algorithm)\n        m_algorithm->removeTarget(t);\n\n    if (m_parent) // can be null if the workspace is gone\n        m_parent->updateWindows();\n}\n\nvoid CSpace::setAlgorithmProvider(SP<CAlgorithm> algo) {\n    m_algorithm = algo;\n}\n\nvoid CSpace::recheckWorkArea() {\n    if (!m_parent || !m_parent->m_monitor) {\n        Log::logger->log(Log::ERR, \"CSpace: recheckWorkArea on no parent / mon?!\");\n        return;\n    }\n\n    const auto  WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent.lock());\n\n    auto        workArea = m_parent->m_monitor->logicalBoxMinusReserved();\n\n    static auto PGAPSOUTDATA   = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:gaps_out\");\n    static auto PFLOATGAPSDATA = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:float_gaps\");\n    auto* const PGAPSOUT       = sc<CCssGapData*>((PGAPSOUTDATA.ptr())->getData());\n    auto*       PFLOATGAPS     = sc<CCssGapData*>(PFLOATGAPSDATA.ptr()->getData());\n    if (PFLOATGAPS->m_bottom < 0 || PFLOATGAPS->m_left < 0 || PFLOATGAPS->m_right < 0 || PFLOATGAPS->m_top < 0)\n        PFLOATGAPS = PGAPSOUT;\n\n    auto                   gapsOut   = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT);\n    auto                   gapsFloat = WORKSPACERULE.gapsOut.value_or(*PFLOATGAPS);\n\n    Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left};\n    Desktop::CReservedArea reservedFloatGaps{gapsFloat.m_top, gapsFloat.m_right, gapsFloat.m_bottom, gapsFloat.m_left};\n\n    auto                   floatWorkArea = workArea;\n\n    reservedFloatGaps.applyip(floatWorkArea);\n    reservedGaps.applyip(workArea);\n\n    m_workArea         = workArea;\n    m_floatingWorkArea = floatWorkArea;\n}\n\nconst CBox& CSpace::workArea(bool floating) const {\n    return floating ? m_floatingWorkArea : m_workArea;\n}\n\nPHLWORKSPACE CSpace::workspace() const {\n    return m_parent.lock();\n}\n\nvoid CSpace::toggleTargetFloating(SP<ITarget> t) {\n    t->setWasTiling(true);\n    m_algorithm->setFloating(t, !t->floating());\n    t->setWasTiling(false);\n\n    m_parent->updateWindows();\n\n    recalculate();\n}\n\nCBox CSpace::targetPositionLocal(SP<ITarget> t) const {\n    return t->position().translate(-m_workArea.pos());\n}\n\nvoid CSpace::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {\n    if (!m_algorithm)\n        return;\n\n    m_algorithm->resizeTarget(Δ, target, corner);\n}\n\nvoid CSpace::moveTarget(const Vector2D& Δ, SP<ITarget> target) {\n    if (!m_algorithm)\n        return;\n\n    m_algorithm->moveTarget(Δ, target);\n}\n\nSP<CAlgorithm> CSpace::algorithm() const {\n    return m_algorithm;\n}\n\nvoid CSpace::recalculate() {\n    recheckWorkArea();\n\n    if (m_algorithm)\n        m_algorithm->recalculate();\n}\n\nvoid CSpace::setFullscreen(SP<ITarget> t, eFullscreenMode mode) {\n    t->setFullscreenMode(mode);\n\n    if (mode == FSMODE_NONE && m_algorithm && t->floating())\n        m_algorithm->recenter(t);\n\n    recalculate();\n}\n\nstd::expected<void, std::string> CSpace::layoutMsg(const std::string_view& sv) {\n    if (m_algorithm)\n        return m_algorithm->layoutMsg(sv);\n\n    return {};\n}\n\nstd::optional<Vector2D> CSpace::predictSizeForNewTiledTarget() {\n    if (m_algorithm)\n        return m_algorithm->predictSizeForNewTiledTarget();\n\n    return std::nullopt;\n}\n\nvoid CSpace::swap(SP<ITarget> a, SP<ITarget> b) {\n    for (auto& t : m_targets) {\n        if (t == a)\n            t = b;\n        else if (t == b)\n            t = a;\n    }\n\n    if (m_algorithm)\n        m_algorithm->swapTargets(a, b);\n}\n\nvoid CSpace::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {\n    if (m_algorithm)\n        m_algorithm->moveTargetInDirection(t, dir, silent);\n}\n\nvoid CSpace::setTargetGeom(const CBox& box, SP<ITarget> target) {\n    if (m_algorithm)\n        m_algorithm->setTargetGeom(box, target);\n}\n\nSP<ITarget> CSpace::getNextCandidate(SP<ITarget> old) {\n    return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old);\n}\n\nconst std::vector<WP<ITarget>>& CSpace::targets() const {\n    return m_targets;\n}\n"
  },
  {
    "path": "src/layout/space/Space.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/math/Direction.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n\n#include \"../../desktop/DesktopTypes.hpp\"\n\n#include \"../LayoutManager.hpp\"\n\n#include <optional>\n#include <expected>\n\nnamespace Layout {\n    class ITarget;\n    class CAlgorithm;\n\n    class CSpace {\n      public:\n        static SP<CSpace> create(PHLWORKSPACE w);\n        ~CSpace() = default;\n\n        void                             add(SP<ITarget> t);\n        void                             remove(SP<ITarget> t);\n        void                             move(SP<ITarget> t, std::optional<Vector2D> focalPoint = std::nullopt);\n\n        void                             swap(SP<ITarget> a, SP<ITarget> b);\n\n        SP<ITarget>                      getNextCandidate(SP<ITarget> old);\n\n        void                             setAlgorithmProvider(SP<CAlgorithm> algo);\n        void                             recheckWorkArea();\n        void                             setFullscreen(SP<ITarget> t, eFullscreenMode mode);\n\n        void                             moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);\n\n        void                             recalculate();\n\n        void                             toggleTargetFloating(SP<ITarget> t);\n\n        std::expected<void, std::string> layoutMsg(const std::string_view& sv);\n        std::optional<Vector2D>          predictSizeForNewTiledTarget();\n\n        const CBox&                      workArea(bool floating = false) const;\n        PHLWORKSPACE                     workspace() const;\n        CBox                             targetPositionLocal(SP<ITarget> t) const;\n\n        void                             resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);\n        void                             moveTarget(const Vector2D& Δ, SP<ITarget> target);\n        void                             setTargetGeom(const CBox& box, SP<ITarget> target); // only for float\n\n        SP<CAlgorithm>                   algorithm() const;\n\n        const std::vector<WP<ITarget>>&  targets() const;\n\n      private:\n        CSpace(PHLWORKSPACE parent);\n\n        WP<CSpace>               m_self;\n\n        std::vector<WP<ITarget>> m_targets;\n        SP<CAlgorithm>           m_algorithm;\n        PHLWORKSPACEREF          m_parent;\n\n        // work area is in global coords\n        CBox m_workArea, m_floatingWorkArea;\n\n        // for recalc\n        CHyprSignalListener m_geomUpdateCallback;\n    };\n};"
  },
  {
    "path": "src/layout/supplementary/DragController.cpp",
    "content": "#include \"DragController.hpp\"\n\n#include \"../space/Space.hpp\"\n\n#include \"../../Compositor.hpp\"\n#include \"../../managers/cursor/CursorShapeOverrideController.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../desktop/view/Group.hpp\"\n#include \"../../render/Renderer.hpp\"\n\nusing namespace Layout;\nusing namespace Layout::Supplementary;\n\nSP<ITarget> CDragStateController::target() const {\n    return m_target.lock();\n}\n\neMouseBindMode CDragStateController::mode() const {\n    return m_dragMode;\n}\n\nbool CDragStateController::wasDraggingWindow() const {\n    return m_wasDraggingWindow;\n}\n\nbool CDragStateController::dragThresholdReached() const {\n    return m_dragThresholdReached;\n}\n\nvoid CDragStateController::resetDragThresholdReached() {\n    m_dragThresholdReached = false;\n}\n\nbool CDragStateController::draggingTiled() const {\n    return m_draggingTiled;\n}\n\nbool CDragStateController::updateDragWindow() {\n    const auto DRAGGINGTARGET = m_target.lock();\n    const bool WAS_FULLSCREEN = DRAGGINGTARGET->fullscreenMode() != FSMODE_NONE;\n\n    if (m_dragThresholdReached) {\n        if (WAS_FULLSCREEN) {\n            Log::logger->log(Log::DEBUG, \"Dragging a fullscreen window\");\n            g_pCompositor->setWindowFullscreenInternal(DRAGGINGTARGET->window(), FSMODE_NONE);\n        }\n\n        const auto PWORKSPACE     = DRAGGINGTARGET->workspace();\n        const auto DRAGGINGWINDOW = DRAGGINGTARGET->window();\n\n        if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGTARGET->floating() || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) {\n            Log::logger->log(Log::DEBUG, \"Rejecting drag on a fullscreen workspace. (window under fullscreen)\");\n            CKeybindManager::changeMouseBindMode(MBIND_INVALID);\n            return true;\n        }\n    }\n\n    m_draggingTiled                   = false;\n    m_draggingWindowOriginalFloatSize = DRAGGINGTARGET->lastFloatingSize();\n\n    if (WAS_FULLSCREEN && DRAGGINGTARGET->floating() && m_dragThresholdReached) {\n        const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();\n        DRAGGINGTARGET->setPositionGlobal(CBox{MOUSECOORDS - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()});\n    } else if (!DRAGGINGTARGET->floating() && m_dragMode == MBIND_MOVE) {\n        Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});\n        DRAGGINGTARGET->rememberFloatingSize((DRAGGINGTARGET->position().size() * 0.8489).clamp(MINSIZE, Vector2D{}).floor());\n\n        if (m_dragThresholdReached) {\n            DRAGGINGTARGET->setPositionGlobal(CBox{g_pInputManager->getMouseCoordsInternal() - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()});\n            g_layoutManager->changeFloatingMode(DRAGGINGTARGET);\n            m_draggingTiled = true;\n        }\n    }\n\n    const auto DRAG_ORIGINAL_BOX = DRAGGINGTARGET->position();\n\n    m_beginDragXY         = g_pInputManager->getMouseCoordsInternal();\n    m_beginDragPositionXY = DRAG_ORIGINAL_BOX.pos();\n    m_beginDragSizeXY     = DRAG_ORIGINAL_BOX.size();\n    m_lastDragXY          = m_beginDragXY;\n\n    return false;\n}\n\nvoid CDragStateController::dragBegin(SP<ITarget> target, eMouseBindMode mode) {\n    m_target   = target;\n    m_dragMode = mode;\n\n    const auto  DRAGGINGTARGET = m_target.lock();\n    static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>(\"binds:drag_threshold\");\n\n    m_mouseMoveEventCount = 1;\n    m_beginDragSizeXY     = Vector2D();\n\n    // Window will be floating. Let's check if it's valid. It should be, but I don't like crashing.\n    if (!validMapped(DRAGGINGTARGET->window())) {\n        Log::logger->log(Log::ERR, \"Dragging attempted on an invalid window (not mapped)\");\n        CKeybindManager::changeMouseBindMode(MBIND_INVALID);\n        return;\n    }\n\n    if (!DRAGGINGTARGET->workspace()) {\n        Log::logger->log(Log::ERR, \"Dragging attempted on an invalid window (no workspace)\");\n        CKeybindManager::changeMouseBindMode(MBIND_INVALID);\n        return;\n    }\n\n    // Try to pick up dragged window now if drag_threshold is disabled\n    // or at least update dragging related variables for the cursors\n    m_dragThresholdReached = *PDRAGTHRESHOLD <= 0;\n    if (updateDragWindow())\n        return;\n\n    // get the grab corner\n    static auto RESIZECORNER = CConfigValue<Hyprlang::INT>(\"general:resize_corner\");\n    if (*RESIZECORNER != 0 && *RESIZECORNER <= 4 && DRAGGINGTARGET->floating()) {\n        switch (*RESIZECORNER) {\n            case 1:\n                m_grabbedCorner = CORNER_TOPLEFT;\n                Cursor::overrideController->setOverride(\"nw-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n                break;\n            case 2:\n                m_grabbedCorner = CORNER_TOPRIGHT;\n                Cursor::overrideController->setOverride(\"ne-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n                break;\n            case 3:\n                m_grabbedCorner = CORNER_BOTTOMRIGHT;\n                Cursor::overrideController->setOverride(\"se-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n                break;\n            case 4:\n                m_grabbedCorner = CORNER_BOTTOMLEFT;\n                Cursor::overrideController->setOverride(\"sw-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n                break;\n        }\n    } else if (m_beginDragXY.x < m_beginDragPositionXY.x + m_beginDragSizeXY.x / 2.F) {\n        if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) {\n            m_grabbedCorner = CORNER_TOPLEFT;\n            Cursor::overrideController->setOverride(\"nw-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n        } else {\n            m_grabbedCorner = CORNER_BOTTOMLEFT;\n            Cursor::overrideController->setOverride(\"sw-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n        }\n    } else {\n        if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) {\n            m_grabbedCorner = CORNER_TOPRIGHT;\n            Cursor::overrideController->setOverride(\"ne-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n        } else {\n            m_grabbedCorner = CORNER_BOTTOMRIGHT;\n            Cursor::overrideController->setOverride(\"se-resize\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n        }\n    }\n\n    if (m_dragMode != MBIND_RESIZE && m_dragMode != MBIND_RESIZE_FORCE_RATIO && m_dragMode != MBIND_RESIZE_BLOCK_RATIO)\n        Cursor::overrideController->setOverride(\"grabbing\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n\n    DRAGGINGTARGET->damageEntire();\n\n    g_pKeybindManager->shadowKeybinds();\n\n    if (DRAGGINGTARGET->window()) {\n        Desktop::focusState()->rawWindowFocus(DRAGGINGTARGET->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);\n        g_pCompositor->changeWindowZOrder(DRAGGINGTARGET->window(), true);\n    }\n}\nvoid CDragStateController::dragEnd() {\n    auto draggingTarget = m_target.lock();\n\n    m_mouseMoveEventCount = 1;\n\n    if (!validMapped(draggingTarget->window())) {\n        if (draggingTarget->window()) {\n            Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n            m_target.reset();\n        }\n        return;\n    }\n\n    Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n    m_target.reset();\n    m_wasDraggingWindow = true;\n\n    if (m_dragMode == MBIND_MOVE && draggingTarget->window()) {\n        draggingTarget->damageEntire();\n\n        const auto DRAGGING_WINDOW = draggingTarget->window();\n\n        const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();\n        PHLWINDOW  pWindow =\n            g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGING_WINDOW);\n\n        if (pWindow) {\n            if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGING_WINDOW))\n                return;\n\n            const bool  FLOATEDINTOTILED = !pWindow->m_isFloating && !m_draggingTiled;\n            static auto PDRAGINTOGROUP   = CConfigValue<Hyprlang::INT>(\"group:drag_into_group\");\n\n            if (pWindow->m_group && DRAGGING_WINDOW->canBeGroupedInto(pWindow->m_group) && *PDRAGINTOGROUP == 1 && !FLOATEDINTOTILED) {\n                pWindow->m_group->add(DRAGGING_WINDOW);\n                // fix the draggingTarget, now it's DRAGGING_WINDOW\n                draggingTarget = DRAGGING_WINDOW->m_target;\n            }\n        }\n    }\n\n    if (m_draggingTiled) {\n        // make sure to check if we are floating because drag into group could make us tiled already\n        if (draggingTarget->floating())\n            g_layoutManager->changeFloatingMode(draggingTarget);\n\n        draggingTarget->rememberFloatingSize(m_draggingWindowOriginalFloatSize);\n    }\n\n    draggingTarget->damageEntire();\n\n    g_layoutManager->setTargetGeom(draggingTarget->position(), draggingTarget);\n\n    Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);\n\n    m_wasDraggingWindow = false;\n    m_dragMode          = MBIND_INVALID;\n}\n\nvoid CDragStateController::mouseMove(const Vector2D& mousePos) {\n    if (m_target.expired())\n        return;\n\n    const auto  DRAGGINGTARGET = m_target.lock();\n    static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>(\"binds:drag_threshold\");\n\n    // Window invalid or drag begin size 0,0 meaning we rejected it.\n    if ((!validMapped(DRAGGINGTARGET->window()) || m_beginDragSizeXY == Vector2D())) {\n        CKeybindManager::changeMouseBindMode(MBIND_INVALID);\n        return;\n    }\n\n    // Yoink dragged window here instead if using drag_threshold and it has been reached\n    if (*PDRAGTHRESHOLD > 0 && !m_dragThresholdReached) {\n        if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY))\n            return;\n        m_dragThresholdReached = true;\n        if (updateDragWindow())\n            return;\n    }\n\n    static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER;\n\n    const auto  DELTA     = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y);\n    const auto  TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y);\n\n    static auto SNAPENABLED = CConfigValue<Hyprlang::INT>(\"general:snap:enabled\");\n\n    const auto  TIMERDELTA    = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - TIMER).count();\n    const auto  MSDELTA       = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - MSTIMER).count();\n    const auto  MSMONITOR     = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate;\n    static int  totalMs       = 0;\n    bool        canSkipUpdate = true;\n\n    MSTIMER = std::chrono::high_resolution_clock::now();\n\n    if (m_mouseMoveEventCount == 1)\n        totalMs = 0;\n\n    if (MSMONITOR > 16.0) {\n        totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount);\n        m_mouseMoveEventCount += 1;\n\n        // check if time-window is enough to skip update on 60hz monitor\n        canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount;\n    }\n\n    if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (m_dragMode != MBIND_MOVE)))\n        return;\n\n    TIMER = std::chrono::high_resolution_clock::now();\n\n    m_lastDragXY = mousePos;\n\n    DRAGGINGTARGET->damageEntire();\n\n    if (m_dragMode == MBIND_MOVE) {\n\n        Vector2D newPos  = m_beginDragPositionXY + DELTA;\n        Vector2D newSize = DRAGGINGTARGET->position().size();\n\n        if (*SNAPENABLED && !m_draggingTiled)\n            g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, MBIND_MOVE, -1, m_beginDragSizeXY);\n\n        newPos = newPos.round();\n\n        DRAGGINGTARGET->setPositionGlobal({newPos, newSize});\n        DRAGGINGTARGET->warpPositionSize();\n    } else if (m_dragMode == MBIND_RESIZE || m_dragMode == MBIND_RESIZE_FORCE_RATIO || m_dragMode == MBIND_RESIZE_BLOCK_RATIO) {\n        if (DRAGGINGTARGET->floating()) {\n\n            Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});\n            Vector2D MAXSIZE = DRAGGINGTARGET->maxSize().value_or(Math::VECTOR2D_MAX);\n\n            Vector2D newSize = m_beginDragSizeXY;\n            Vector2D newPos  = m_beginDragPositionXY;\n\n            if (m_grabbedCorner == CORNER_BOTTOMRIGHT)\n                newSize = newSize + DELTA;\n            else if (m_grabbedCorner == CORNER_TOPLEFT)\n                newSize = newSize - DELTA;\n            else if (m_grabbedCorner == CORNER_TOPRIGHT)\n                newSize = newSize + Vector2D(DELTA.x, -DELTA.y);\n            else if (m_grabbedCorner == CORNER_BOTTOMLEFT)\n                newSize = newSize + Vector2D(-DELTA.x, DELTA.y);\n\n            eMouseBindMode mode = m_dragMode;\n            if (DRAGGINGTARGET->window() && DRAGGINGTARGET->window()->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO)\n                mode = MBIND_RESIZE_FORCE_RATIO;\n\n            if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) {\n\n                const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x;\n\n                if (MINSIZE.x * RATIO > MINSIZE.y)\n                    MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO);\n                else\n                    MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y);\n\n                if (MAXSIZE.x * RATIO < MAXSIZE.y)\n                    MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO);\n                else\n                    MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y);\n\n                if (newSize.x * RATIO > newSize.y)\n                    newSize = Vector2D(newSize.x, newSize.x * RATIO);\n                else\n                    newSize = Vector2D(newSize.y / RATIO, newSize.y);\n            }\n\n            newSize = newSize.clamp(MINSIZE, MAXSIZE);\n\n            if (m_grabbedCorner == CORNER_TOPLEFT)\n                newPos = newPos - newSize + m_beginDragSizeXY;\n            else if (m_grabbedCorner == CORNER_TOPRIGHT)\n                newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y);\n            else if (m_grabbedCorner == CORNER_BOTTOMLEFT)\n                newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0);\n\n            if (*SNAPENABLED) {\n                g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, mode, m_grabbedCorner, m_beginDragSizeXY);\n                newSize = newSize.clamp(MINSIZE, MAXSIZE);\n            }\n\n            CBox wb = {newPos, newSize};\n            wb.round();\n\n            DRAGGINGTARGET->setPositionGlobal(wb);\n            DRAGGINGTARGET->warpPositionSize();\n        } else {\n            g_layoutManager->resizeTarget(TICKDELTA, DRAGGINGTARGET, m_grabbedCorner);\n            DRAGGINGTARGET->warpPositionSize();\n        }\n    }\n\n    // get middle point\n    Vector2D middle = DRAGGINGTARGET->position().middle();\n\n    // and check its monitor\n    const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle);\n\n    if (PMONITOR && PMONITOR->m_activeWorkspace && DRAGGINGTARGET->floating() /* If we're resaizing a tiled target, don't do this */) {\n        const auto WS = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace;\n        DRAGGINGTARGET->assignToSpace(WS->m_space);\n    }\n\n    DRAGGINGTARGET->damageEntire();\n}\n"
  },
  {
    "path": "src/layout/supplementary/DragController.hpp",
    "content": "#pragma once\n\n#include \"../target/Target.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n\nnamespace Layout {\n    enum eRectCorner : uint8_t;\n}\n\nnamespace Layout::Supplementary {\n\n    // DragStateController contains logic to begin and end a drag, which shouldn't be part of the layout's job. It's stuff like\n    // toggling float when dragging tiled, remembering sizes, checking deltas, etc.\n    class CDragStateController {\n      public:\n        CDragStateController()  = default;\n        ~CDragStateController() = default;\n\n        void           dragBegin(SP<ITarget> target, eMouseBindMode mode);\n        void           dragEnd();\n\n        void           mouseMove(const Vector2D& mousePos);\n        eMouseBindMode mode() const;\n        bool           wasDraggingWindow() const;\n        bool           dragThresholdReached() const;\n        void           resetDragThresholdReached();\n        bool           draggingTiled() const;\n\n        /*\n            Called to try to pick up window for dragging.\n            Updates drag related variables and floats window if threshold reached.\n            Return true to reject\n        */\n        bool        updateDragWindow();\n\n        SP<ITarget> target() const;\n\n      private:\n        WP<ITarget>         m_target;\n\n        eMouseBindMode      m_dragMode             = MBIND_INVALID;\n        bool                m_wasDraggingWindow    = false;\n        bool                m_dragThresholdReached = false;\n        bool                m_draggingTiled        = false;\n\n        int                 m_mouseMoveEventCount = 0;\n        Vector2D            m_beginDragXY;\n        Vector2D            m_lastDragXY;\n        Vector2D            m_beginDragPositionXY;\n        Vector2D            m_beginDragSizeXY;\n        Vector2D            m_draggingWindowOriginalFloatSize;\n        Layout::eRectCorner m_grabbedCorner = sc<Layout::eRectCorner>(0) /* CORNER_NONE */;\n    };\n};\n"
  },
  {
    "path": "src/layout/supplementary/WorkspaceAlgoMatcher.cpp",
    "content": "#include \"WorkspaceAlgoMatcher.hpp\"\n\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n\n#include \"../algorithm/Algorithm.hpp\"\n#include \"../space/Space.hpp\"\n\n#include \"../algorithm/floating/default/DefaultFloatingAlgorithm.hpp\"\n#include \"../algorithm/tiled/dwindle/DwindleAlgorithm.hpp\"\n#include \"../algorithm/tiled/master/MasterAlgorithm.hpp\"\n#include \"../algorithm/tiled/scrolling/ScrollingAlgorithm.hpp\"\n#include \"../algorithm/tiled/monocle/MonocleAlgorithm.hpp\"\n\n#include \"../../Compositor.hpp\"\n\nusing namespace Layout;\nusing namespace Layout::Supplementary;\n\nconstexpr const char*            DEFAULT_FLOATING_ALGO = \"default\";\nconstexpr const char*            DEFAULT_TILED_ALGO    = \"dwindle\";\n\nconst UP<CWorkspaceAlgoMatcher>& Supplementary::algoMatcher() {\n    static UP<CWorkspaceAlgoMatcher> m = makeUnique<CWorkspaceAlgoMatcher>();\n    return m;\n}\n\nCWorkspaceAlgoMatcher::CWorkspaceAlgoMatcher() {\n    m_tiledAlgos = {\n        {\"dwindle\", [] { return makeUnique<Tiled::CDwindleAlgorithm>(); }},\n        {\"master\", [] { return makeUnique<Tiled::CMasterAlgorithm>(); }},\n        {\"scrolling\", [] { return makeUnique<Tiled::CScrollingAlgorithm>(); }},\n        {\"monocle\", [] { return makeUnique<Tiled::CMonocleAlgorithm>(); }},\n    };\n\n    m_floatingAlgos = {\n        {\"default\", [] { return makeUnique<Floating::CDefaultFloatingAlgorithm>(); }},\n    };\n\n    m_algoNames = {\n        {&typeid(Tiled::CDwindleAlgorithm), \"dwindle\"},\n        {&typeid(Tiled::CMasterAlgorithm), \"master\"},\n        {&typeid(Tiled::CScrollingAlgorithm), \"scrolling\"},\n        {&typeid(Tiled::CMonocleAlgorithm), \"monocle\"},\n        {&typeid(Floating::CDefaultFloatingAlgorithm), \"default\"},\n    };\n}\n\nbool CWorkspaceAlgoMatcher::registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<ITiledAlgorithm>()>&& factory) {\n    if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name))\n        return false;\n\n    m_tiledAlgos.emplace(name, std::move(factory));\n    m_algoNames.emplace(typeInfo, name);\n\n    updateWorkspaceLayouts();\n\n    return true;\n}\n\nbool CWorkspaceAlgoMatcher::registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<IFloatingAlgorithm>()>&& factory) {\n    if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name))\n        return false;\n\n    m_floatingAlgos.emplace(name, std::move(factory));\n    m_algoNames.emplace(typeInfo, name);\n\n    updateWorkspaceLayouts();\n\n    return true;\n}\n\nbool CWorkspaceAlgoMatcher::unregisterAlgo(const std::string& name) {\n    if (!m_tiledAlgos.contains(name) && !m_floatingAlgos.contains(name))\n        return false;\n\n    std::erase_if(m_algoNames, [&name](const auto& e) { return e.second == name; });\n\n    if (m_floatingAlgos.contains(name))\n        m_floatingAlgos.erase(name);\n    else\n        m_tiledAlgos.erase(name);\n\n    // this is needed here to avoid situations where a plugin unloads and we still have a UP\n    // to a plugin layout\n    updateWorkspaceLayouts();\n\n    return true;\n}\n\nUP<ITiledAlgorithm> CWorkspaceAlgoMatcher::algoForNameTiled(const std::string& s) {\n    if (m_tiledAlgos.contains(s))\n        return m_tiledAlgos.at(s)();\n    return m_tiledAlgos.at(DEFAULT_TILED_ALGO)();\n}\n\nUP<IFloatingAlgorithm> CWorkspaceAlgoMatcher::algoForNameFloat(const std::string& s) {\n    if (m_floatingAlgos.contains(s))\n        return m_floatingAlgos.at(s)();\n    return m_floatingAlgos.at(DEFAULT_FLOATING_ALGO)();\n}\n\nstd::string CWorkspaceAlgoMatcher::tiledAlgoForWorkspace(const PHLWORKSPACE& w) {\n    static auto PLAYOUT = CConfigValue<Hyprlang::STRING>(\"general:layout\");\n\n    auto        rule = g_pConfigManager->getWorkspaceRuleFor(w);\n    return rule.layout.value_or(*PLAYOUT);\n}\n\nSP<CAlgorithm> CWorkspaceAlgoMatcher::createAlgorithmForWorkspace(PHLWORKSPACE w) {\n    return CAlgorithm::create(algoForNameTiled(tiledAlgoForWorkspace(w)), makeUnique<Floating::CDefaultFloatingAlgorithm>(), w->m_space);\n}\n\nvoid CWorkspaceAlgoMatcher::updateWorkspaceLayouts() {\n    // TODO: make this ID-based, string comparison is slow\n    for (const auto& ws : g_pCompositor->getWorkspaces()) {\n        if (!ws)\n            continue;\n\n        const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo();\n\n        if (!TILED_ALGO)\n            continue;\n\n        const auto LAYOUT_TO_USE = tiledAlgoForWorkspace(ws.lock());\n\n        if (m_algoNames.contains(&typeid(*TILED_ALGO.get())) && m_algoNames.at(&typeid(*TILED_ALGO.get())) == LAYOUT_TO_USE)\n            continue;\n\n        // needs a switchup\n        ws->m_space->algorithm()->updateTiledAlgo(algoForNameTiled(LAYOUT_TO_USE));\n    }\n}\n\nstd::string CWorkspaceAlgoMatcher::getNameForTiledAlgo(const std::type_info* type) {\n    if (m_algoNames.contains(type))\n        return m_algoNames.at(type);\n    return \"unknown\";\n}\n"
  },
  {
    "path": "src/layout/supplementary/WorkspaceAlgoMatcher.hpp",
    "content": "#pragma once\n\n#include \"../../desktop/DesktopTypes.hpp\"\n\n#include <map>\n#include <type_traits>\n#include <functional>\n#include <string>\n\nnamespace Layout {\n    class CAlgorithm;\n    class ITiledAlgorithm;\n    class IFloatingAlgorithm;\n}\n\nnamespace Layout::Supplementary {\n    class CWorkspaceAlgoMatcher {\n      public:\n        CWorkspaceAlgoMatcher();\n        ~CWorkspaceAlgoMatcher() = default;\n\n        SP<CAlgorithm> createAlgorithmForWorkspace(PHLWORKSPACE w);\n        void           updateWorkspaceLayouts();\n        std::string    getNameForTiledAlgo(const std::type_info* type);\n\n        // these fns can fail due to name collisions\n        bool registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<ITiledAlgorithm>()>&& factory);\n        bool registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function<UP<IFloatingAlgorithm>()>&& factory);\n\n        // this fn fails if the algo isn't registered\n        bool unregisterAlgo(const std::string& name);\n\n      private:\n        UP<ITiledAlgorithm>                                            algoForNameTiled(const std::string& s);\n        UP<IFloatingAlgorithm>                                         algoForNameFloat(const std::string& s);\n\n        std::string                                                    tiledAlgoForWorkspace(const PHLWORKSPACE&);\n\n        std::map<std::string, std::function<UP<ITiledAlgorithm>()>>    m_tiledAlgos;\n        std::map<std::string, std::function<UP<IFloatingAlgorithm>()>> m_floatingAlgos;\n\n        std::map<const std::type_info*, std::string>                   m_algoNames;\n    };\n\n    const UP<CWorkspaceAlgoMatcher>& algoMatcher();\n}"
  },
  {
    "path": "src/layout/target/Target.cpp",
    "content": "#include \"Target.hpp\"\n#include \"../space/Space.hpp\"\n#include \"../../debug/log/Logger.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Layout;\nusing namespace Hyprutils::Utils;\n\nvoid ITarget::setPositionGlobal(const STargetBox& box) {\n    m_box = box;\n    m_box.logicalBox.round();\n    m_box.visualBox.round();\n}\n\nvoid ITarget::setPositionGlobal(const CBox& box) {\n    setPositionGlobal({.logicalBox = box});\n}\n\nvoid ITarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {\n    if (m_space == space && !m_ghostSpace)\n        return;\n\n    const bool HAD_SPACE = !!m_space;\n\n    if (m_space && !m_ghostSpace)\n        m_space->remove(m_self.lock());\n\n    m_space = space;\n\n    if (space && HAD_SPACE)\n        space->move(m_self.lock(), focalPoint);\n    else if (space)\n        space->add(m_self.lock());\n\n    if (!space)\n        Log::logger->log(Log::WARN, \"ITarget: assignToSpace with a null space?\");\n\n    m_ghostSpace = false;\n\n    onUpdateSpace();\n}\n\nvoid ITarget::setSpaceGhost(const SP<CSpace>& space) {\n    if (m_space)\n        assignToSpace(nullptr);\n\n    m_space = space;\n\n    m_ghostSpace = true;\n}\n\nSP<CSpace> ITarget::space() const {\n    return m_space;\n}\n\nPHLWORKSPACE ITarget::workspace() const {\n    if (!m_space)\n        return nullptr;\n\n    return m_space->workspace();\n}\n\nCBox ITarget::position() const {\n    return m_box.logicalBox;\n}\n\nvoid ITarget::rememberFloatingSize(const Vector2D& size) {\n    m_floatingSize = size;\n}\n\nVector2D ITarget::lastFloatingSize() const {\n    return m_floatingSize;\n}\n\nvoid ITarget::recalc() {\n    setPositionGlobal(m_box);\n}\n\nvoid ITarget::setPseudo(bool x) {\n    if (m_pseudo == x)\n        return;\n\n    m_pseudo = x;\n\n    recalc();\n}\n\nbool ITarget::isPseudo() const {\n    return m_pseudo;\n}\n\nvoid ITarget::setPseudoSize(const Vector2D& size) {\n    m_pseudoSize = size;\n\n    recalc();\n}\n\nVector2D ITarget::pseudoSize() {\n    return m_pseudoSize;\n}\n\nvoid ITarget::swap(SP<ITarget> b) {\n    const auto IS_FLOATING   = floating();\n    const auto IS_FLOATING_B = b->floating();\n\n    // Keep workspaces alive during a swap: moving one window will unref the ws\n\n    // NOLINTNEXTLINE\n    const auto PWS1 = workspace();\n    // NOLINTNEXTLINE\n    const auto  PWS2 = b->workspace();\n\n    CScopeGuard x([&] {\n        b->setFloating(IS_FLOATING);\n        setFloating(IS_FLOATING_B);\n\n        // update the spaces\n        b->onUpdateSpace();\n        onUpdateSpace();\n    });\n\n    if (b->space() == m_space) {\n        // simplest\n        m_space->swap(m_self.lock(), b);\n        m_space->recalculate();\n        return;\n    }\n\n    // spaces differ\n    if (m_space)\n        m_space->swap(m_self.lock(), b);\n    if (b->space())\n        b->space()->swap(b, m_self.lock());\n\n    std::swap(m_space, b->m_space);\n\n    // recalc both\n    if (m_space)\n        m_space->recalculate();\n    if (b->space())\n        b->space()->recalculate();\n}\n\nbool ITarget::wasTiling() const {\n    return m_wasTiling;\n}\n\nvoid ITarget::setWasTiling(bool x) {\n    m_wasTiling = x;\n}\n"
  },
  {
    "path": "src/layout/target/Target.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../desktop/Workspace.hpp\"\n\n#include <expected>\n#include <cstdint>\n\nnamespace Layout {\n    enum eTargetType : uint8_t {\n        TARGET_TYPE_WINDOW = 0,\n        TARGET_TYPE_GROUP,\n    };\n\n    enum eGeometryFailure : uint8_t {\n        GEOMETRY_NO_DESIRED      = 0,\n        GEOMETRY_INVALID_DESIRED = 1,\n    };\n\n    class CSpace;\n\n    struct SGeometryRequested {\n        Vector2D                size;\n        std::optional<Vector2D> pos;\n    };\n\n    struct STargetBox {\n        CBox logicalBox;\n        CBox visualBox;\n    };\n\n    class ITarget {\n      public:\n        virtual ~ITarget() = default;\n\n        virtual eTargetType type() = 0;\n\n        // position is within its space\n        virtual void         setPositionGlobal(const STargetBox& box);\n        void                 setPositionGlobal(const CBox& box);\n        virtual CBox         position() const;\n        virtual void         assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual void         setSpaceGhost(const SP<CSpace>& space);\n        virtual SP<CSpace>   space() const;\n        virtual PHLWORKSPACE workspace() const;\n        virtual PHLWINDOW    window() const = 0;\n        virtual void         recalc();\n        virtual bool         wasTiling() const;\n        virtual void         setWasTiling(bool x);\n\n        virtual void         rememberFloatingSize(const Vector2D& size);\n        virtual Vector2D     lastFloatingSize() const;\n\n        virtual void         setPseudo(bool x);\n        virtual bool         isPseudo() const;\n        virtual void         setPseudoSize(const Vector2D& size);\n        virtual Vector2D     pseudoSize();\n        virtual void         swap(SP<ITarget> b);\n\n        //\n        virtual bool                                                floating()                              = 0;\n        virtual void                                                setFloating(bool x)                     = 0;\n        virtual std::expected<SGeometryRequested, eGeometryFailure> desiredGeometry()                       = 0;\n        virtual eFullscreenMode                                     fullscreenMode()                        = 0;\n        virtual void                                                setFullscreenMode(eFullscreenMode mode) = 0;\n        virtual std::optional<Vector2D>                             minSize()                               = 0;\n        virtual std::optional<Vector2D>                             maxSize()                               = 0;\n        virtual void                                                damageEntire()                          = 0;\n        virtual void                                                warpPositionSize()                      = 0;\n        virtual void                                                onUpdateSpace()                         = 0;\n\n      protected:\n        ITarget() = default;\n\n        STargetBox  m_box;\n        SP<CSpace>  m_space;\n        WP<ITarget> m_self;\n        Vector2D    m_floatingSize;\n        bool        m_pseudo     = false;\n        bool        m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout\n        Vector2D    m_pseudoSize = {1280, 720};\n        bool        m_wasTiling  = false;\n    };\n};\n"
  },
  {
    "path": "src/layout/target/WindowGroupTarget.cpp",
    "content": "#include \"WindowGroupTarget.hpp\"\n\n#include \"../space/Space.hpp\"\n#include \"../algorithm/Algorithm.hpp\"\n#include \"WindowTarget.hpp\"\n#include \"Target.hpp\"\n\n#include \"../../render/Renderer.hpp\"\n\nusing namespace Layout;\n\nSP<CWindowGroupTarget> CWindowGroupTarget::create(SP<Desktop::View::CGroup> g) {\n    auto target    = SP<CWindowGroupTarget>(new CWindowGroupTarget(g));\n    target->m_self = target;\n    return target;\n}\n\nCWindowGroupTarget::CWindowGroupTarget(SP<Desktop::View::CGroup> g) : m_group(g) {\n    ;\n}\n\neTargetType CWindowGroupTarget::type() {\n    return TARGET_TYPE_GROUP;\n}\n\nvoid CWindowGroupTarget::setPositionGlobal(const STargetBox& box) {\n    ITarget::setPositionGlobal(box);\n\n    updatePos();\n}\n\nvoid CWindowGroupTarget::updatePos() {\n    for (const auto& w : m_group->windows()) {\n        w->m_target->setPositionGlobal(m_box);\n    }\n}\n\nvoid CWindowGroupTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {\n    ITarget::assignToSpace(space, focalPoint);\n\n    if (space)\n        m_group->updateWorkspace(space->workspace());\n}\n\nbool CWindowGroupTarget::floating() {\n    return m_group->current()->m_target->floating();\n}\n\nvoid CWindowGroupTarget::setFloating(bool x) {\n    for (const auto& w : m_group->windows()) {\n        w->m_target->setFloating(x);\n    }\n}\n\nstd::expected<SGeometryRequested, eGeometryFailure> CWindowGroupTarget::desiredGeometry() {\n    return m_group->current()->m_target->desiredGeometry();\n}\n\nPHLWINDOW CWindowGroupTarget::window() const {\n    return m_group->current();\n}\n\neFullscreenMode CWindowGroupTarget::fullscreenMode() {\n    return m_group->current()->m_fullscreenState.internal;\n}\n\nvoid CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) {\n    m_group->current()->m_fullscreenState.internal = mode;\n}\n\nstd::optional<Vector2D> CWindowGroupTarget::minSize() {\n    return m_group->current()->minSize();\n}\n\nstd::optional<Vector2D> CWindowGroupTarget::maxSize() {\n    return m_group->current()->maxSize();\n}\n\nvoid CWindowGroupTarget::damageEntire() {\n    g_pHyprRenderer->damageWindow(m_group->current());\n}\n\nvoid CWindowGroupTarget::warpPositionSize() {\n    for (const auto& w : m_group->windows()) {\n        w->m_target->warpPositionSize();\n    }\n}\n\nvoid CWindowGroupTarget::onUpdateSpace() {\n    for (const auto& w : m_group->windows()) {\n        w->m_target->onUpdateSpace();\n    }\n}\n"
  },
  {
    "path": "src/layout/target/WindowGroupTarget.hpp",
    "content": "#pragma once\n\n#include \"Target.hpp\"\n\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../desktop/view/Group.hpp\"\n\nnamespace Layout {\n\n    class CWindowGroupTarget : public ITarget {\n      public:\n        static SP<CWindowGroupTarget> create(SP<Desktop::View::CGroup> g);\n        virtual ~CWindowGroupTarget() = default;\n\n        virtual eTargetType                                         type();\n\n        virtual void                                                setPositionGlobal(const STargetBox& box);\n        virtual void                                                assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual PHLWINDOW                                           window() const;\n\n        virtual bool                                                floating();\n        virtual void                                                setFloating(bool x);\n        virtual std::expected<SGeometryRequested, eGeometryFailure> desiredGeometry();\n        virtual eFullscreenMode                                     fullscreenMode();\n        virtual void                                                setFullscreenMode(eFullscreenMode mode);\n        virtual std::optional<Vector2D>                             minSize();\n        virtual std::optional<Vector2D>                             maxSize();\n        virtual void                                                damageEntire();\n        virtual void                                                warpPositionSize();\n        virtual void                                                onUpdateSpace();\n\n      private:\n        CWindowGroupTarget(SP<Desktop::View::CGroup> g);\n\n        void                      updatePos();\n\n        WP<Desktop::View::CGroup> m_group;\n    };\n};\n"
  },
  {
    "path": "src/layout/target/WindowTarget.cpp",
    "content": "#include \"WindowTarget.hpp\"\n\n#include \"../space/Space.hpp\"\n#include \"../algorithm/Algorithm.hpp\"\n\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../xwayland/XSurface.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../render/Renderer.hpp\"\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Hyprutils::Utils;\nusing namespace Layout;\n\nSP<ITarget> CWindowTarget::create(PHLWINDOW w) {\n    auto target    = SP<CWindowTarget>(new CWindowTarget(w));\n    target->m_self = target;\n    return target;\n}\n\nCWindowTarget::CWindowTarget(PHLWINDOW w) : m_window(w) {\n    ;\n}\n\neTargetType CWindowTarget::type() {\n    return TARGET_TYPE_WINDOW;\n}\n\nvoid CWindowTarget::setPositionGlobal(const STargetBox& box) {\n    ITarget::setPositionGlobal(box);\n\n    updatePos();\n}\n\nvoid CWindowTarget::updatePos() {\n    g_pHyprRenderer->damageWindow(m_window.lock());\n    CScopeGuard x([this] { g_pHyprRenderer->damageWindow(m_window.lock()); });\n\n    if (!m_space)\n        return;\n\n    if (fullscreenMode() == FSMODE_FULLSCREEN)\n        return;\n\n    if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) {\n        m_window->m_position = m_box.logicalBox.pos();\n        m_window->m_size     = m_box.logicalBox.size();\n\n        *m_window->m_realPosition = m_box.logicalBox.pos();\n        *m_window->m_realSize     = m_box.logicalBox.size();\n\n        m_window->sendWindowSize();\n        m_window->updateWindowDecos();\n\n        return;\n    }\n\n    // Tiled is more complicated.\n\n    // if we are in maximized, force the box to be max work area.\n    // TODO: this shouldn't be here.\n    if (fullscreenMode() == FSMODE_MAXIMIZED)\n        ITarget::setPositionGlobal({.logicalBox = m_space->workArea(floating())});\n\n    const auto PMONITOR         = m_space->workspace()->m_monitor;\n    const auto PWORKSPACE       = m_space->workspace();\n    const auto MONITOR_WORKAREA = m_space->workArea();\n\n    // get specific gaps and rules for this workspace,\n    // if user specified them in config\n    const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE);\n\n    if (!validMapped(m_window)) {\n        if (m_window)\n            g_layoutManager->removeTarget(m_window->layoutTarget());\n        return;\n    }\n\n    if (fullscreenMode() == FSMODE_FULLSCREEN)\n        return;\n\n    g_pHyprRenderer->damageWindow(window());\n\n    CBox nodeBox = m_box.logicalBox;\n    nodeBox.round();\n\n    m_window->m_size     = nodeBox.size();\n    m_window->m_position = nodeBox.pos();\n\n    auto calcPos  = m_box.visualBox.pos();\n    auto calcSize = m_box.visualBox.size();\n\n    if (m_box.visualBox.empty()) {\n        calcPos  = nodeBox.pos();\n        calcSize = nodeBox.size();\n        // for gaps outer\n        const bool DISPLAYLEFT   = STICKS(m_box.logicalBox.x, MONITOR_WORKAREA.x);\n        const bool DISPLAYRIGHT  = STICKS(m_box.logicalBox.x + m_box.logicalBox.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);\n        const bool DISPLAYTOP    = STICKS(m_box.logicalBox.y, MONITOR_WORKAREA.y);\n        const bool DISPLAYBOTTOM = STICKS(m_box.logicalBox.y + m_box.logicalBox.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);\n\n        // this is used for scrolling, so that the gaps are correct when a window is the full width and has neighbors\n        const bool        DISPLAYINVERSELEFT  = STICKS(m_box.logicalBox.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);\n        const bool        DISPLAYINVERSERIGHT = STICKS(m_box.logicalBox.x + m_box.logicalBox.w, MONITOR_WORKAREA.x);\n\n        static auto       PGAPSINDATA = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:gaps_in\");\n        auto* const       PGAPSIN     = sc<CCssGapData*>((PGAPSINDATA.ptr())->getData());\n        auto              gapsIn      = WORKSPACERULE.gapsIn.value_or(*PGAPSIN);\n\n        const static auto REQUESTEDRATIO          = CConfigValue<Hyprlang::VEC2>(\"layout:single_window_aspect_ratio\");\n        const static auto REQUESTEDRATIOTOLERANCE = CConfigValue<Hyprlang::FLOAT>(\"layout:single_window_aspect_ratio_tolerance\");\n\n        Vector2D          ratioPadding;\n\n        if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && fullscreenMode() == FSMODE_NONE) {\n            const Vector2D originalSize = MONITOR_WORKAREA.size();\n\n            const double   requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;\n            const double   originalRatio  = originalSize.x / originalSize.y;\n\n            if (requestedRatio > originalRatio) {\n                double padding = originalSize.y - (originalSize.x / requestedRatio);\n\n                if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y)\n                    ratioPadding = Vector2D{0., padding};\n            } else if (requestedRatio < originalRatio) {\n                double padding = originalSize.x - (originalSize.y * requestedRatio);\n\n                if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x)\n                    ratioPadding = Vector2D{padding, 0.};\n            }\n        }\n\n        const auto GAPOFFSETTOPLEFT = Vector2D(sc<double>(DISPLAYLEFT ? 0 : (DISPLAYINVERSELEFT ? 2 * gapsIn.m_left : gapsIn.m_left)), sc<double>(DISPLAYTOP ? 0 : gapsIn.m_top));\n\n        const auto GAPOFFSETBOTTOMRIGHT =\n            Vector2D(sc<double>(DISPLAYRIGHT ? 0 : (DISPLAYINVERSERIGHT ? 2 * gapsIn.m_right : gapsIn.m_right)), sc<double>(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom));\n\n        calcPos  = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2;\n        calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;\n    }\n\n    if (isPseudo() && fullscreenMode() == FSMODE_NONE) {\n        // Calculate pseudo\n        float scale = 1;\n\n        // adjust if doesn't fit\n        if (m_pseudoSize.x > calcSize.x || m_pseudoSize.y > calcSize.y) {\n            if (m_pseudoSize.x > calcSize.x)\n                scale = calcSize.x / m_pseudoSize.x;\n\n            if (m_pseudoSize.y * scale > calcSize.y)\n                scale = calcSize.y / m_pseudoSize.y;\n\n            auto DELTA = calcSize - m_pseudoSize * scale;\n            calcSize   = m_pseudoSize * scale;\n            calcPos    = calcPos + DELTA / 2.f; // center\n        } else {\n            auto DELTA = calcSize - m_pseudoSize;\n            calcPos    = calcPos + DELTA / 2.f; // center\n            calcSize   = m_pseudoSize;\n        }\n    }\n\n    const auto RESERVED = m_window->getFullWindowReservedArea();\n    calcPos             = calcPos + RESERVED.topLeft;\n    calcSize            = calcSize - (RESERVED.topLeft + RESERVED.bottomRight);\n\n    Vector2D    availableSpace = calcSize;\n\n    static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>(\"misc:size_limits_tiled\");\n\n    if (*PCLAMP_TILED) {\n        Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});\n        Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} : m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY});\n        calcSize         = calcSize.clamp(minSize, maxSize);\n\n        calcPos += (availableSpace - calcSize) / 2.0;\n\n        calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x);\n        calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y);\n    }\n\n    if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) {\n        // if special, we adjust the coords a bit\n        static auto PSCALEFACTOR = CConfigValue<Hyprlang::FLOAT>(\"dwindle:special_scale_factor\");\n\n        CBox        wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR};\n        wb.round(); // avoid rounding mess\n\n        *m_window->m_realPosition = wb.pos();\n        *m_window->m_realSize     = wb.size();\n    } else {\n        CBox wb = {calcPos, calcSize};\n        wb.round(); // avoid rounding mess\n\n        *m_window->m_realSize     = wb.size();\n        *m_window->m_realPosition = wb.pos();\n    }\n\n    m_window->updateWindowDecos();\n}\n\nvoid CWindowTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {\n    if (!space) {\n        ITarget::assignToSpace(space, focalPoint);\n        return;\n    }\n\n    // keep the ref here so that moveToWorkspace doesn't unref the workspace\n    // and assignToSpace doesn't think this is a new target because space wp is dead\n    const auto WSREF = space->workspace();\n\n    m_window->m_monitor = space->workspace()->m_monitor;\n    m_window->moveToWorkspace(space->workspace());\n\n    // layout and various update fns want the target to already have m_workspace set\n    ITarget::assignToSpace(space, focalPoint);\n\n    m_window->updateToplevel();\n    m_window->updateWindowDecos();\n}\n\nbool CWindowTarget::floating() {\n    return m_window->m_isFloating;\n}\n\nvoid CWindowTarget::setFloating(bool x) {\n    if (x == m_window->m_isFloating)\n        return;\n\n    m_window->m_isFloating = x;\n    m_window->m_pinned     = false;\n\n    m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FLOATING);\n}\n\nVector2D CWindowTarget::clampSizeForDesired(const Vector2D& size) const {\n    Vector2D newSize = size;\n    if (const auto m = m_window->minSize(); m)\n        newSize = newSize.clamp(*m);\n    if (const auto m = m_window->maxSize(); m)\n        newSize = newSize.clamp(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}, *m);\n    return newSize;\n}\n\nstd::expected<SGeometryRequested, eGeometryFailure> CWindowTarget::desiredGeometry() {\n\n    SGeometryRequested requested;\n\n    CBox               DESIRED_GEOM = g_pXWaylandManager->getGeometryForWindow(m_window.lock());\n    const auto         PMONITOR     = m_window->m_monitor.lock();\n\n    requested.size = clampSizeForDesired(DESIRED_GEOM.size());\n\n    if (m_window->m_isX11) {\n        Vector2D xy    = {DESIRED_GEOM.x, DESIRED_GEOM.y};\n        xy             = g_pXWaylandManager->xwaylandToWaylandCoords(xy);\n        requested.pos  = xy;\n        DESIRED_GEOM.x = xy.x;\n        DESIRED_GEOM.y = xy.y;\n    }\n\n    const auto STOREDSIZE = m_window->m_ruleApplicator->persistentSize().valueOrDefault() ? g_pConfigManager->getStoredFloatingSize(m_window.lock()) : std::nullopt;\n\n    if (STOREDSIZE)\n        requested.size = clampSizeForDesired(*STOREDSIZE);\n\n    if (!PMONITOR) {\n        Log::logger->log(Log::ERR, \"{:m} has an invalid monitor in desiredGeometry!\", m_window.lock());\n        return std::unexpected(GEOMETRY_NO_DESIRED);\n    }\n\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n    const auto  toLogical          = [&](SGeometryRequested& req) {\n        if (m_window->m_isX11 && *PXWLFORCESCALEZERO && PMONITOR)\n            req.size /= PMONITOR->m_scale;\n    };\n\n    if (DESIRED_GEOM.width <= 2 || DESIRED_GEOM.height <= 2) {\n        const auto SURFACE = m_window->wlSurface()->resource();\n\n        if (SURFACE->m_current.size.x > 5 && SURFACE->m_current.size.y > 5) {\n            // center on mon and call it a day\n            requested.pos.reset();\n            requested.size = clampSizeForDesired(SURFACE->m_current.size);\n            toLogical(requested);\n            return requested;\n        }\n\n        if (m_window->m_isX11 && m_window->isX11OverrideRedirect()) {\n            // check OR windows, they like their shit\n            const auto SIZE = clampSizeForDesired(m_window->m_xwaylandSurface->m_geometry.w > 0 && m_window->m_xwaylandSurface->m_geometry.h > 0 ?\n                                                      m_window->m_xwaylandSurface->m_geometry.size() :\n                                                      Vector2D{600, 400});\n\n            if (m_window->m_xwaylandSurface->m_geometry.x != 0 && m_window->m_xwaylandSurface->m_geometry.y != 0) {\n                requested.size = SIZE;\n                requested.pos  = g_pXWaylandManager->xwaylandToWaylandCoords(m_window->m_xwaylandSurface->m_geometry.pos());\n                toLogical(requested);\n                return requested;\n            }\n        }\n\n        return std::unexpected(m_window->m_isX11 && m_window->isX11OverrideRedirect() ? GEOMETRY_INVALID_DESIRED : GEOMETRY_NO_DESIRED);\n    }\n\n    // TODO: detect a popup in a more consistent way.\n    if ((DESIRED_GEOM.x == 0 && DESIRED_GEOM.y == 0) || !m_window->m_isX11) {\n        // middle of parent if available\n        if (!m_window->m_isX11) {\n            if (const auto PARENT = m_window->parent(); PARENT) {\n                const auto POS = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - DESIRED_GEOM.size() / 2.F;\n                requested.pos  = POS;\n            }\n        }\n    } else {\n        // if it is, we respect where it wants to put itself, but apply monitor offset if outside\n        // most of these are popups\n\n        Vector2D pos;\n\n        if (const auto POPENMON = g_pCompositor->getMonitorFromVector(DESIRED_GEOM.middle()); POPENMON->m_id != PMONITOR->m_id)\n            pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y) - POPENMON->m_position + PMONITOR->m_position;\n        else\n            pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y);\n\n        requested.pos = pos;\n    }\n\n    if (DESIRED_GEOM.w <= 2 || DESIRED_GEOM.h <= 2)\n        return std::unexpected(GEOMETRY_NO_DESIRED);\n\n    toLogical(requested);\n    return requested;\n}\n\nPHLWINDOW CWindowTarget::window() const {\n    return m_window.lock();\n}\n\neFullscreenMode CWindowTarget::fullscreenMode() {\n    return m_window->m_fullscreenState.internal;\n}\n\nvoid CWindowTarget::setFullscreenMode(eFullscreenMode mode) {\n    if (floating() && m_window->m_fullscreenState.internal == FSMODE_NONE)\n        rememberFloatingSize(m_box.logicalBox.size());\n\n    m_window->m_fullscreenState.internal = mode;\n}\n\nstd::optional<Vector2D> CWindowTarget::minSize() {\n    return m_window->minSize();\n}\n\nstd::optional<Vector2D> CWindowTarget::maxSize() {\n    return m_window->maxSize();\n}\n\nvoid CWindowTarget::damageEntire() {\n    g_pHyprRenderer->damageWindow(m_window.lock());\n}\n\nvoid CWindowTarget::warpPositionSize() {\n    m_window->m_realSize->warp();\n    m_window->m_realPosition->warp();\n    m_window->updateWindowDecos();\n}\n\nvoid CWindowTarget::onUpdateSpace() {\n    if (!space())\n        return;\n\n    m_window->m_monitor = space()->workspace()->m_monitor;\n    m_window->moveToWorkspace(space()->workspace());\n    m_window->updateToplevel();\n    m_window->updateWindowData();\n    m_window->updateWindowDecos();\n}\n"
  },
  {
    "path": "src/layout/target/WindowTarget.hpp",
    "content": "#pragma once\n\n#include \"Target.hpp\"\n\n#include \"../../desktop/view/Window.hpp\"\n\nnamespace Layout {\n\n    class CWindowTarget : public ITarget {\n      public:\n        static SP<ITarget> create(PHLWINDOW w);\n        virtual ~CWindowTarget() = default;\n\n        virtual eTargetType                                         type();\n\n        virtual void                                                setPositionGlobal(const STargetBox& box);\n        virtual void                                                assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint = std::nullopt);\n        virtual PHLWINDOW                                           window() const;\n\n        virtual bool                                                floating();\n        virtual void                                                setFloating(bool x);\n        virtual std::expected<SGeometryRequested, eGeometryFailure> desiredGeometry();\n        virtual eFullscreenMode                                     fullscreenMode();\n        virtual void                                                setFullscreenMode(eFullscreenMode mode);\n        virtual std::optional<Vector2D>                             minSize();\n        virtual std::optional<Vector2D>                             maxSize();\n        virtual void                                                damageEntire();\n        virtual void                                                warpPositionSize();\n        virtual void                                                onUpdateSpace();\n\n      private:\n        CWindowTarget(PHLWINDOW w);\n\n        Vector2D     clampSizeForDesired(const Vector2D& size) const;\n\n        void         updatePos();\n\n        PHLWINDOWREF m_window;\n    };\n};\n"
  },
  {
    "path": "src/macros.hpp",
    "content": "#pragma once\n\n#include <cmath>\n#include <csignal>\n#include <print>\n#include <utility>\n\n#include \"helpers/memory/Memory.hpp\"\n#include \"debug/log/Logger.hpp\"\n\n#ifndef NDEBUG\n#ifdef HYPRLAND_DEBUG\n#define ISDEBUG true\n#else\n#define ISDEBUG false\n#endif\n#else\n#define ISDEBUG false\n#endif\n\n#define SPECIAL_WORKSPACE_START (-99)\n\n#define PI 3.14159265358979\n\n#define STRVAL_EMPTY \"[[EMPTY]]\"\n\n#define WORKSPACE_INVALID     -1L\n#define WORKSPACE_NOT_CHANGED -101\n\n#define MONITOR_INVALID -1L\n\n#define MIN_WINDOW_SIZE 20.0\n\n// max value 32 because killed is a int uniform\n#define POINTER_PRESSED_HISTORY_LENGTH 32\n\n#define VECINRECT(vec, x1, y1, x2, y2)    ((vec).x >= (x1) && (vec).x < (x2) && (vec).y >= (y1) && (vec).y < (y2))\n#define VECNOTINRECT(vec, x1, y1, x2, y2) ((vec).x < (x1) || (vec).x >= (x2) || (vec).y < (y1) || (vec).y >= (y2))\n\n#define DELTALESSTHAN(a, b, delta) (abs((a) - (b)) < (delta))\n\n#define STICKS(a, b) abs((a) - (b)) < 2\n\n#define HYPRATOM(name) {name, 0}\n\n#define RASSERT(expr, reason, ...)                                                                                                                                                 \\\n    if (!(expr)) {                                                                                                                                                                 \\\n        Log::logger->log(Log::CRIT, \"\\n==========================================================================================\\nASSERTION FAILED! \\n\\n{}\\n\\nat: line {} in {}\", \\\n                         std::format(reason, ##__VA_ARGS__), __LINE__,                                                                                                             \\\n                         ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })());                                 \\\n        std::print(\"Assertion failed! See the log in /tmp/hypr/hyprland.log for more info.\");                                                                                      \\\n        raise(SIGABRT);                                                                                                                                                            \\\n    }\n\n#define ASSERT(expr) RASSERT(expr, \"?\")\n\n// absolutely ridiculous formatter spec parsing\n#define FORMAT_PARSE(specs__, type__)                                                                                                                                              \\\n    template <typename FormatContext>                                                                                                                                              \\\n    constexpr auto parse(FormatContext& ctx) {                                                                                                                                     \\\n        auto it = ctx.begin();                                                                                                                                                     \\\n        for (; it != ctx.end() && *it != '}'; it++) {                                                                                                                              \\\n            switch (*it) { specs__ default : throw std::format_error(\"invalid format specification\"); }                                                                            \\\n        }                                                                                                                                                                          \\\n        return it;                                                                                                                                                                 \\\n    }\n\n#define FORMAT_FLAG(spec__, flag__)                                                                                                                                                \\\n    case spec__: (flag__) = true; break;\n\n#define FORMAT_NUMBER(buf__)                                                                                                                                                       \\\n    case '0':                                                                                                                                                                      \\\n    case '1':                                                                                                                                                                      \\\n    case '2':                                                                                                                                                                      \\\n    case '3':                                                                                                                                                                      \\\n    case '4':                                                                                                                                                                      \\\n    case '5':                                                                                                                                                                      \\\n    case '6':                                                                                                                                                                      \\\n    case '7':                                                                                                                                                                      \\\n    case '8':                                                                                                                                                                      \\\n    case '9': (buf__).push_back(*it); break;\n\n#if ISDEBUG\n#define UNREACHABLE()                                                                                                                                                              \\\n    {                                                                                                                                                                              \\\n        Log::logger->log(Log::CRIT, \"\\n\\nMEMORY CORRUPTED: Unreachable failed! (Reached an unreachable position, memory corruption!!!)\");                                          \\\n        raise(SIGABRT);                                                                                                                                                            \\\n        std::unreachable();                                                                                                                                                        \\\n    }\n#else\n#define UNREACHABLE() std::unreachable();\n#endif\n\n#if ISDEBUG\n\n#define GLCALL(__CALL__)                                                                                                                                                           \\\n    {                                                                                                                                                                              \\\n        __CALL__;                                                                                                                                                                  \\\n        static const auto GLDEBUG = CConfigValue<Hyprlang::INT>(\"debug:gl_debugging\");                                                                                             \\\n        if (*GLDEBUG) {                                                                                                                                                            \\\n            auto err = glGetError();                                                                                                                                               \\\n            if (err != GL_NO_ERROR) {                                                                                                                                              \\\n                Log::logger->log(Log::ERR, \"[GLES] Error in call at {}@{}: 0x{:x}\", __LINE__,                                                                                      \\\n                                 ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err);                    \\\n            }                                                                                                                                                                      \\\n        }                                                                                                                                                                          \\\n    }\n\n#else\n\n#define GLCALL(__CALL__)                                                                                                                                                           \\\n    { __CALL__; }\n\n#endif\n\n#define HYPRUTILS_FORWARD(ns, name)                                                                                                                                                \\\n    namespace Hyprutils {                                                                                                                                                          \\\n        namespace ns {                                                                                                                                                             \\\n            class name;                                                                                                                                                            \\\n        }                                                                                                                                                                          \\\n    }\n\n#define AQUAMARINE_VERSION_NUMBER (AQUAMARINE_VERSION_MAJOR * 10000 + AQUAMARINE_VERSION_MINOR * 100 + AQUAMARINE_VERSION_PATCH)\n#define AQUAMARINE_FORWARD(name)                                                                                                                                                   \\\n    namespace Aquamarine {                                                                                                                                                         \\\n        class name;                                                                                                                                                                \\\n    }\n\n#define UNLIKELY(expr) (expr) [[unlikely]]\n#define LIKELY(expr)   (expr) [[likely]]\n"
  },
  {
    "path": "src/main.cpp",
    "content": "#include \"defines.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"Compositor.hpp\"\n#include \"config/ConfigManager.hpp\"\n#include \"init/initHelpers.hpp\"\n#include \"debug/HyprCtl.hpp\"\n#include \"helpers/env/Env.hpp\"\n\n#include <csignal>\n#include <cstdio>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/memory/Casts.hpp>\n#include <print>\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::Memory;\n\n#include <fcntl.h>\n#include <iostream>\n#include <iterator>\n#include <vector>\n#include <stdexcept>\n#include <string>\n#include <string_view>\n#include <span>\n#include <filesystem>\n\nstatic void help() {\n    std::println(\"usage: Hyprland [arg [...]].\\n\");\n    std::println(R\"#(Arguments:\n    --help              -h       - Show this message again\n    --config FILE       -c FILE  - Specify config file to use\n    --socket NAME                - Sets the Wayland socket name (for Wayland socket handover)\n    --wayland-fd FD              - Sets the Wayland socket fd (for Wayland socket handover)\n    --watchdog-fd FD             - Used by start-hyprland\n    --safe-mode                  - Starts Hyprland in safe mode\n    --systeminfo                 - Prints system infos\n    --i-am-really-stupid         - Omits root user privileges check (why would you do that?)\n    --verify-config              - Do not run Hyprland, only print if the config has any errors\n    --version           -v       - Print this binary's version\n    --version-json               - Print this binary's version as json)#\");\n}\n\nstatic void reapZombieChildrenAutomatically() {\n    struct sigaction act;\n    act.sa_handler = SIG_DFL;\n    sigemptyset(&act.sa_mask);\n    act.sa_flags = SA_NOCLDWAIT;\n#ifdef SA_RESTORER\n    act.sa_restorer = NULL;\n#endif\n    sigaction(SIGCHLD, &act, nullptr);\n}\n\nint main(int argc, char** argv) {\n\n    if (!getenv(\"XDG_RUNTIME_DIR\"))\n        throwError(\"XDG_RUNTIME_DIR is not set!\");\n\n    // export HYPRLAND_CMD\n    std::string cmd = argv[0];\n    for (int i = 1; i < argc; ++i)\n        cmd += std::string(\" \") + argv[i];\n\n    setenv(\"HYPRLAND_CMD\", cmd.c_str(), 1);\n    setenv(\"XDG_BACKEND\", \"wayland\", 1);\n    setenv(\"XDG_SESSION_TYPE\", \"wayland\", 1);\n    setenv(\"_JAVA_AWT_WM_NONREPARENTING\", \"1\", 1);\n    setenv(\"MOZ_ENABLE_WAYLAND\", \"1\", 1);\n\n    // parse some args\n    std::string configPath;\n    std::string socketName;\n    int         socketFd   = -1;\n    bool        ignoreSudo = false, verifyConfig = false, safeMode = false;\n    int         watchdogFd = -1;\n\n    if (argc > 1) {\n        std::span<char*> args{argv + 1, sc<std::size_t>(argc - 1)};\n\n        for (auto it = args.begin(); it != args.end(); it++) {\n            std::string_view value = *it;\n\n            if (value == \"--i-am-really-stupid\" && !ignoreSudo) {\n                std::println(\"[ WARNING ] Running Hyprland with superuser privileges might damage your system\");\n\n                ignoreSudo = true;\n            } else if (value == \"--socket\") {\n                if (std::next(it) == args.end()) {\n                    help();\n\n                    return 1;\n                }\n\n                socketName = *std::next(it);\n                it++;\n            } else if (value == \"--wayland-fd\") {\n                if (std::next(it) == args.end()) {\n                    help();\n\n                    return 1;\n                }\n\n                try {\n                    socketFd = std::stoi(*std::next(it));\n\n                    // check if socketFd is a valid file descriptor\n                    if (fcntl(socketFd, F_GETFD) == -1)\n                        throw std::exception();\n                } catch (...) {\n                    std::println(stderr, \"[ ERROR ] Invalid Wayland FD!\");\n                    help();\n\n                    return 1;\n                }\n\n                it++;\n            } else if (value == \"-c\" || value == \"--config\") {\n                if (std::next(it) == args.end()) {\n                    help();\n\n                    return 1;\n                }\n                configPath = *std::next(it);\n\n                try {\n                    configPath = std::filesystem::canonical(configPath);\n\n                    if (!std::filesystem::is_regular_file(configPath)) {\n                        throw std::exception();\n                    }\n                } catch (...) {\n                    std::println(stderr, \"[ ERROR ] Config file '{}' doesn't exist!\", configPath);\n                    help();\n\n                    return 1;\n                }\n\n                Log::logger->log(Log::DEBUG, \"User-specified config location: '{}'\", configPath);\n\n                it++;\n\n                continue;\n            } else if (value == \"-h\" || value == \"--help\") {\n                help();\n\n                return 0;\n            } else if (value == \"-v\" || value == \"--version\") {\n                std::println(\"{}\", versionRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, \"\"));\n                return 0;\n            } else if (value == \"--version-json\") {\n                std::println(\"{}\", versionRequest(eHyprCtlOutputFormat::FORMAT_JSON, \"\"));\n                return 0;\n            } else if (value == \"--systeminfo\") {\n                std::println(\"{}\", systemInfoRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, \"\"));\n                return 0;\n            } else if (value == \"--verify-config\") {\n                verifyConfig = true;\n                continue;\n            } else if (value == \"--safe-mode\") {\n                safeMode = true;\n                continue;\n            } else if (value == \"--watchdog-fd\") {\n                if (std::next(it) == args.end()) {\n                    help();\n                    return 1;\n                }\n\n                try {\n                    watchdogFd = std::stoi(*std::next(it));\n                    it++;\n                } catch (...) {\n                    std::println(stderr, \"[ ERROR ] Invalid fd for watchdog fd\");\n                    help();\n                    return 1;\n                }\n            } else {\n                std::println(stderr, \"[ ERROR ] Unknown option '{}' !\", value);\n                help();\n\n                return 1;\n            }\n        }\n    }\n\n    if (!ignoreSudo && NInit::isSudo()) {\n        std::println(stderr,\n                     \"[ ERROR ] Hyprland was launched with superuser privileges, but the privileges check is not omitted.\\n\"\n                     \"          Hint: Use the --i-am-really-stupid flag to omit that check.\");\n\n        return 1;\n    } else if (ignoreSudo && NInit::isSudo())\n        std::println(\"Superuser privileges check is omitted. I hope you know what you're doing.\");\n\n    if (socketName.empty() ^ (socketFd == -1)) {\n        std::println(stderr,\n                     \"[ ERROR ] Hyprland was launched with only one of --socket and --wayland-fd.\\n\"\n                     \"          Hint: Pass both --socket and --wayland-fd to perform Wayland socket handover.\");\n\n        return 1;\n    }\n\n    if (!verifyConfig)\n        std::println(\"Welcome to Hyprland!\");\n\n    // let's init the compositor.\n    // it initializes basic Wayland stuff in the constructor.\n    try {\n        g_pCompositor                       = makeUnique<CCompositor>(verifyConfig);\n        g_pCompositor->m_explicitConfigPath = configPath;\n    } catch (const std::exception& e) {\n        std::println(stderr, \"Hyprland threw in ctor: {}\\nCannot continue.\", e.what());\n        return 1;\n    }\n\n    reapZombieChildrenAutomatically();\n\n    bool watchdogOk = watchdogFd > 0;\n\n    if (watchdogFd > 0)\n        watchdogOk = g_pCompositor->setWatchdogFd(watchdogFd);\n    if (safeMode)\n        g_pCompositor->m_safeMode = true;\n\n    if (!watchdogOk && !verifyConfig)\n        Log::logger->log(Log::WARN, \"WARNING: Hyprland is being launched without start-hyprland. This is highly advised against.\");\n\n    g_pCompositor->initServer(socketName, socketFd);\n\n    if (verifyConfig)\n        return !g_pConfigManager->m_lastConfigVerificationWasSuccessful;\n\n    if (!Env::envEnabled(\"HYPRLAND_NO_RT\"))\n        NInit::gainRealTime();\n\n    Log::logger->log(Log::DEBUG, \"Hyprland init finished.\");\n\n    // If all's good to go, start.\n    g_pCompositor->startCompositor();\n\n    g_pCompositor->cleanup();\n\n    g_pCompositor.reset();\n\n    Log::logger->log(Log::DEBUG, \"Hyprland has reached the end.\");\n\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "src/managers/ANRManager.cpp",
    "content": "#include \"ANRManager.hpp\"\n\n#include \"../helpers/fs/FsUtils.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../macros.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../protocols/XDGShell.hpp\"\n#include \"./eventLoop/EventLoopManager.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../xwayland/XSurface.hpp\"\n#include \"../i18n/Engine.hpp\"\n#include \"../event/EventBus.hpp\"\n\nusing namespace Hyprutils::OS;\n\nstatic constexpr auto TIMER_TIMEOUT = std::chrono::milliseconds(1500);\n\nCANRManager::CANRManager() {\n    if (!NFsUtils::executableExistsInPath(\"hyprland-dialog\")) {\n        Log::logger->log(Log::ERR, \"hyprland-dialog missing from PATH, cannot start ANRManager\");\n        return;\n    }\n\n    m_timer = makeShared<CEventLoopTimer>(TIMER_TIMEOUT, [this](SP<CEventLoopTimer> self, void* data) { onTick(); }, this);\n    g_pEventLoopManager->addTimer(m_timer);\n\n    m_active = true;\n\n    static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) {\n        for (const auto& d : m_data) {\n            // Window is ANR dialog\n            if (d->isRunning() && d->dialogBox->getPID() == window->getPID())\n                return;\n\n            if (d->fitsWindow(window))\n                return;\n        }\n\n        m_data.emplace_back(makeShared<SANRData>(window));\n    });\n\n    static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) {\n        for (const auto& d : m_data) {\n            if (!d->fitsWindow(window))\n                continue;\n\n            // kill the dialog, act as if we got a \"ping\" in case there's more than one\n            // window from this client, in which case the dialog will re-appear.\n            d->killDialog();\n            d->missedResponses = 0;\n            d->dialogSaidWait  = false;\n        }\n\n        std::erase_if(m_data, [&window](auto& w) { return w == window; });\n    });\n\n    m_timer->updateTimeout(TIMER_TIMEOUT);\n}\n\nvoid CANRManager::onTick() {\n    static auto PENABLEANR    = CConfigValue<Hyprlang::INT>(\"misc:enable_anr_dialog\");\n    static auto PANRTHRESHOLD = CConfigValue<Hyprlang::INT>(\"misc:anr_missed_pings\");\n\n    if (!*PENABLEANR) {\n        m_timer->updateTimeout(TIMER_TIMEOUT * 10);\n        return;\n    }\n\n    for (auto& data : m_data) {\n        PHLWINDOW firstWindow;\n        int       count = 0;\n        for (const auto& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped)\n                continue;\n\n            if (!data->fitsWindow(w))\n                continue;\n\n            count++;\n            if (!firstWindow)\n                firstWindow = w;\n        }\n\n        if (count == 0)\n            continue;\n\n        if (data->missedResponses >= *PANRTHRESHOLD) {\n            if (!data->isRunning() && !data->dialogSaidWait) {\n                data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPID());\n\n                for (const auto& w : g_pCompositor->m_windows) {\n                    if (!w->m_isMapped)\n                        continue;\n\n                    if (!data->fitsWindow(w))\n                        continue;\n\n                    *w->m_notRespondingTint = 0.2F;\n                }\n            }\n        } else if (data->isRunning())\n            data->killDialog();\n\n        if (data->missedResponses == 0)\n            data->dialogSaidWait = false;\n\n        data->missedResponses++;\n\n        data->ping();\n    }\n\n    m_timer->updateTimeout(TIMER_TIMEOUT);\n}\n\nvoid CANRManager::onResponse(SP<CXDGWMBase> wmBase) {\n    const auto DATA = dataFor(wmBase);\n\n    if (!DATA)\n        return;\n\n    onResponse(DATA);\n}\n\nvoid CANRManager::onResponse(SP<CXWaylandSurface> pXwaylandSurface) {\n    const auto DATA = dataFor(pXwaylandSurface);\n\n    if (!DATA)\n        return;\n\n    onResponse(DATA);\n}\n\nvoid CANRManager::onResponse(SP<CANRManager::SANRData> data) {\n    data->missedResponses = 0;\n    if (data->isRunning())\n        data->killDialog();\n}\n\nbool CANRManager::isNotResponding(PHLWINDOW pWindow) {\n    const auto DATA = dataFor(pWindow);\n\n    if (!DATA)\n        return false;\n\n    return isNotResponding(DATA);\n}\n\nbool CANRManager::isNotResponding(SP<CANRManager::SANRData> data) {\n    static auto PANRTHRESHOLD = CConfigValue<Hyprlang::INT>(\"misc:anr_missed_pings\");\n    return data->missedResponses > *PANRTHRESHOLD;\n}\n\nSP<CANRManager::SANRData> CANRManager::dataFor(PHLWINDOW pWindow) {\n    auto it = m_data.end();\n    if (pWindow->m_xwaylandSurface)\n        it = std::ranges::find_if(m_data, [&pWindow](const auto& data) { return data->xwaylandSurface && data->xwaylandSurface == pWindow->m_xwaylandSurface; });\n    else if (pWindow->m_xdgSurface)\n        it = std::ranges::find_if(m_data, [&pWindow](const auto& data) { return data->xdgBase && data->xdgBase == pWindow->m_xdgSurface->m_owner; });\n    return it == m_data.end() ? nullptr : *it;\n}\n\nSP<CANRManager::SANRData> CANRManager::dataFor(SP<CXDGWMBase> wmBase) {\n    auto it = std::ranges::find_if(m_data, [&wmBase](const auto& data) { return data->xdgBase && data->xdgBase == wmBase; });\n    return it == m_data.end() ? nullptr : *it;\n}\n\nSP<CANRManager::SANRData> CANRManager::dataFor(SP<CXWaylandSurface> pXwaylandSurface) {\n    auto it = std::ranges::find_if(m_data, [&pXwaylandSurface](const auto& data) { return data->xwaylandSurface && data->xwaylandSurface == pXwaylandSurface; });\n    return it == m_data.end() ? nullptr : *it;\n}\n\nCANRManager::SANRData::SANRData(PHLWINDOW pWindow) :\n    xwaylandSurface(pWindow->m_xwaylandSurface), xdgBase(pWindow->m_xdgSurface ? pWindow->m_xdgSurface->m_owner : WP<CXDGWMBase>{}) {\n    ;\n}\n\nCANRManager::SANRData::~SANRData() {\n    if (dialogBox && dialogBox->isRunning())\n        killDialog();\n}\n\nvoid CANRManager::SANRData::runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID) {\n    if (dialogBox && dialogBox->isRunning())\n        killDialog();\n\n    const auto OPTION_TERMINATE_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_TERMINATE, {});\n    const auto OPTION_WAIT_STR      = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_WAIT, {});\n    const auto OPTIONS              = std::vector{OPTION_TERMINATE_STR, OPTION_WAIT_STR};\n    const auto CLASS_STR            = appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass;\n    const auto TITLE_STR            = appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName;\n    const auto DESCRIPTION_STR      = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, {{\"title\", TITLE_STR}, {\"class\", CLASS_STR}});\n\n    dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), DESCRIPTION_STR, OPTIONS);\n\n    for (const auto& w : g_pCompositor->m_windows) {\n        if (!w->m_isMapped)\n            continue;\n\n        if (!fitsWindow(w))\n            continue;\n\n        if (w->m_workspace)\n            dialogBox->setExecRule(std::format(\"workspace {} silent\", w->m_workspace->getConfigName()));\n\n        break;\n    }\n\n    dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP<CPromiseResult<std::string>> r) {\n        if (r->hasError()) {\n            Log::logger->log(Log::ERR, \"CANRManager::SANRData::runDialog: error spawning dialog\");\n            return;\n        }\n\n        const auto& result = r->result();\n\n        if (result.starts_with(OPTION_TERMINATE_STR))\n            ::kill(dialogWmPID, SIGKILL);\n        else if (result.starts_with(OPTION_WAIT_STR))\n            dialogSaidWait = true;\n        else\n            Log::logger->log(Log::ERR, \"CANRManager::SANRData::runDialog: lambda: unrecognized result: {}\", result);\n    });\n}\n\nbool CANRManager::SANRData::isRunning() {\n    return dialogBox && dialogBox->isRunning();\n}\n\nvoid CANRManager::SANRData::killDialog() {\n    if (!dialogBox)\n        return;\n\n    dialogBox->kill();\n    dialogBox = nullptr;\n}\n\nbool CANRManager::SANRData::fitsWindow(PHLWINDOW pWindow) const {\n    if (pWindow->m_xwaylandSurface)\n        return pWindow->m_xwaylandSurface == xwaylandSurface;\n    else if (pWindow->m_xdgSurface)\n        return pWindow->m_xdgSurface->m_owner == xdgBase && xdgBase;\n    return false;\n}\n\nbool CANRManager::SANRData::isDefunct() const {\n    return xdgBase.expired() && xwaylandSurface.expired();\n}\n\npid_t CANRManager::SANRData::getPID() const {\n    if (xdgBase) {\n        pid_t pid = 0;\n        wl_client_get_credentials(xdgBase->client(), &pid, nullptr, nullptr);\n        return pid;\n    }\n\n    if (xwaylandSurface)\n        return xwaylandSurface->m_pid;\n\n    return 0;\n}\n\nvoid CANRManager::SANRData::ping() {\n    if (xdgBase) {\n        xdgBase->ping();\n        return;\n    }\n\n    if (xwaylandSurface)\n        xwaylandSurface->ping();\n}\n"
  },
  {
    "path": "src/managers/ANRManager.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include <chrono>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include \"./eventLoop/EventLoopTimer.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/AsyncDialogBox.hpp\"\n#include <vector>\n\nclass CXDGWMBase;\nclass CXWaylandSurface;\n\nclass CANRManager {\n  public:\n    CANRManager();\n\n    void onResponse(SP<CXDGWMBase> wmBase);\n    void onResponse(SP<CXWaylandSurface> xwaylandSurface);\n    bool isNotResponding(PHLWINDOW pWindow);\n\n  private:\n    bool                m_active = false;\n    SP<CEventLoopTimer> m_timer;\n\n    void                onTick();\n\n    struct SANRData {\n        SANRData(PHLWINDOW pWindow);\n        ~SANRData();\n\n        WP<CXWaylandSurface> xwaylandSurface;\n        WP<CXDGWMBase>       xdgBase;\n\n        int                  missedResponses = 0;\n\n        bool                 dialogSaidWait = false;\n        SP<CAsyncDialogBox>  dialogBox;\n\n        void                 runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID);\n        bool                 isRunning();\n        void                 killDialog();\n        bool                 isDefunct() const;\n        bool                 fitsWindow(PHLWINDOW pWindow) const;\n        pid_t                getPID() const;\n        void                 ping();\n    };\n\n    void                      onResponse(SP<SANRData> data);\n    bool                      isNotResponding(SP<SANRData> data);\n    SP<SANRData>              dataFor(PHLWINDOW pWindow);\n    SP<SANRData>              dataFor(SP<CXDGWMBase> wmBase);\n    SP<SANRData>              dataFor(SP<CXWaylandSurface> pXwaylandSurface);\n\n    std::vector<SP<SANRData>> m_data;\n};\n\ninline UP<CANRManager> g_pANRManager;\n"
  },
  {
    "path": "src/managers/CursorManager.cpp",
    "content": "#include \"CursorManager.hpp\"\n#include \"Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"PointerManager.hpp\"\n#include \"../xwayland/XWayland.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../event/EventBus.hpp\"\n\nstatic int cursorAnimTimer(SP<CEventLoopTimer> self, void* data) {\n    const auto cursorMgr = sc<CCursorManager*>(data);\n    cursorMgr->tickAnimatedCursor();\n    return 1;\n}\n\nstatic void hcLogger(enum eHyprcursorLogLevel level, char* message) {\n    if (level == HC_LOG_TRACE)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"[hc] {}\", message);\n}\n\nCCursorBuffer::CCursorBuffer(cairo_surface_t* surf, const Vector2D& size_, const Vector2D& hot_) : m_hotspot(hot_), m_stride(cairo_image_surface_get_stride(surf)) {\n    size = size_;\n\n    m_data = std::vector<uint8_t>(cairo_image_surface_get_data(surf), cairo_image_surface_get_data(surf) + (cairo_image_surface_get_height(surf) * m_stride));\n}\n\nCCursorBuffer::CCursorBuffer(const uint8_t* pixelData, const Vector2D& size_, const Vector2D& hot_) : m_hotspot(hot_), m_stride(4 * size_.x) {\n    size = size_;\n\n    m_data = std::vector<uint8_t>(pixelData, pixelData + (sc<int>(size_.y) * m_stride));\n}\n\nAquamarine::eBufferCapability CCursorBuffer::caps() {\n    return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;\n}\n\nAquamarine::eBufferType CCursorBuffer::type() {\n    return Aquamarine::eBufferType::BUFFER_TYPE_SHM;\n}\n\nvoid CCursorBuffer::update(const Hyprutils::Math::CRegion& damage) {\n    ;\n}\n\nbool CCursorBuffer::isSynchronous() {\n    return true;\n}\n\nbool CCursorBuffer::good() {\n    return true;\n}\n\nAquamarine::SSHMAttrs CCursorBuffer::shm() {\n    Aquamarine::SSHMAttrs attrs;\n    attrs.success = true;\n    attrs.format  = DRM_FORMAT_ARGB8888;\n    attrs.size    = size;\n    attrs.stride  = m_stride;\n    return attrs;\n}\n\nstd::tuple<uint8_t*, uint32_t, size_t> CCursorBuffer::beginDataPtr(uint32_t flags) {\n    return {m_data.data(), DRM_FORMAT_ARGB8888, m_stride};\n}\n\nvoid CCursorBuffer::endDataPtr() {\n    ;\n}\n\nCCursorManager::CCursorManager() {\n    m_hyprcursor               = makeUnique<Hyprcursor::CHyprcursorManager>(m_theme.empty() ? nullptr : m_theme.c_str(), hcLogger);\n    m_xcursor                  = makeUnique<CXCursorManager>();\n    static auto PUSEHYPRCURSOR = CConfigValue<Hyprlang::INT>(\"cursor:enable_hyprcursor\");\n\n    if (m_hyprcursor->valid() && *PUSEHYPRCURSOR) {\n        // find default size. First, HYPRCURSOR_SIZE then default to 24\n        auto const* SIZE = getenv(\"HYPRCURSOR_SIZE\");\n        if (SIZE) {\n            try {\n                m_size = std::stoi(SIZE);\n            } catch (...) { ; }\n        }\n\n        if (m_size <= 0) {\n            Log::logger->log(Log::WARN, \"HYPRCURSOR_SIZE size not set, defaulting to size 24\");\n            m_size = 24;\n        }\n    } else {\n        Log::logger->log(Log::ERR, \"Hyprcursor failed loading theme \\\"{}\\\", falling back to Xcursor.\", m_theme);\n\n        auto const* SIZE = getenv(\"XCURSOR_SIZE\");\n        if (SIZE) {\n            try {\n                m_size = std::stoi(SIZE);\n            } catch (...) { ; }\n        }\n\n        if (m_size <= 0) {\n            Log::logger->log(Log::WARN, \"XCURSOR_SIZE size not set, defaulting to size 24\");\n            m_size = 24;\n        }\n    }\n\n    // since we fallback to xcursor always load it on startup. otherwise we end up with a empty theme if hyprcursor is enabled in the config\n    // and then later is disabled.\n    m_xcursor->loadTheme(getenv(\"XCURSOR_THEME\") ? getenv(\"XCURSOR_THEME\") : \"default\", m_size, m_cursorScale);\n\n    m_animationTimer = makeShared<CEventLoopTimer>(std::nullopt, cursorAnimTimer, this);\n    g_pEventLoopManager->addTimer(m_animationTimer);\n\n    updateTheme();\n\n    static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { this->updateTheme(); });\n}\n\nCCursorManager::~CCursorManager() {\n    if (m_animationTimer && g_pEventLoopManager) {\n        g_pEventLoopManager->removeTimer(m_animationTimer);\n        m_animationTimer.reset();\n    }\n\n    if (m_hyprcursor->valid() && m_currentStyleInfo.size > 0)\n        m_hyprcursor->cursorSurfaceStyleDone(m_currentStyleInfo);\n}\n\nSP<Aquamarine::IBuffer> CCursorManager::getCursorBuffer() {\n    return !m_cursorBuffers.empty() ? m_cursorBuffers.back() : nullptr;\n}\n\nvoid CCursorManager::setCursorSurface(SP<Desktop::View::CWLSurface> surf, const Vector2D& hotspot) {\n    if (!surf || !surf->resource())\n        g_pPointerManager->resetCursorImage();\n    else\n        g_pPointerManager->setCursorSurface(surf, hotspot);\n\n    m_ourBufferConnected = false;\n}\n\nvoid CCursorManager::setCursorBuffer(SP<CCursorBuffer> buf, const Vector2D& hotspot, const float& scale) {\n    m_cursorBuffers.emplace_back(buf);\n    g_pPointerManager->setCursorBuffer(getCursorBuffer(), hotspot, scale);\n    if (m_cursorBuffers.size() > 1)\n        std::erase_if(m_cursorBuffers, [this](const auto& buf) { return buf.get() == m_cursorBuffers.front().get(); });\n\n    m_ourBufferConnected = true;\n}\n\nvoid CCursorManager::setAnimationTimer(const int& frame, const int& delay) {\n    if (delay > 0) {\n        // arm\n        m_animationTimer->updateTimeout(std::chrono::milliseconds(delay));\n    } else {\n        // disarm\n        m_animationTimer->updateTimeout(std::nullopt);\n    }\n\n    m_currentAnimationFrame = frame;\n}\n\nvoid CCursorManager::setCursorFromName(const std::string& name) {\n\n    static auto PUSEHYPRCURSOR = CConfigValue<Hyprlang::INT>(\"cursor:enable_hyprcursor\");\n\n    auto        setXCursor = [this](auto const& name) {\n        float scale = std::ceil(m_cursorScale);\n\n        auto  xcursor = m_xcursor->getShape(name, m_size, m_cursorScale);\n        auto& icon    = xcursor->images.front();\n        auto  buf     = makeShared<CCursorBuffer>(rc<uint8_t*>(icon.pixels.data()), icon.size, icon.hotspot);\n        setCursorBuffer(buf, icon.hotspot / scale, scale);\n\n        m_currentXcursor = xcursor;\n\n        int delay = 0;\n        int frame = 0;\n        if (m_currentXcursor->images.size() > 1)\n            delay = m_currentXcursor->images[frame].delay;\n\n        setAnimationTimer(frame, delay);\n    };\n\n    auto setHyprCursor = [this](auto const& name) {\n        m_currentCursorShapeData = m_hyprcursor->getShape(name.c_str(), m_currentStyleInfo);\n\n        if (m_currentCursorShapeData.images.empty()) {\n            // try with '_' first (old hc, etc)\n            std::string newName = name;\n            std::ranges::replace(newName, '-', '_');\n\n            m_currentCursorShapeData = m_hyprcursor->getShape(newName.c_str(), m_currentStyleInfo);\n        }\n\n        if (m_currentCursorShapeData.images.empty()) {\n            // fallback to a default if available\n            constexpr const std::array<const char*, 3> fallbackShapes = {\"default\", \"left_ptr\", \"left-ptr\"};\n\n            for (auto const& s : fallbackShapes) {\n                m_currentCursorShapeData = m_hyprcursor->getShape(s, m_currentStyleInfo);\n\n                if (!m_currentCursorShapeData.images.empty())\n                    break;\n            }\n\n            if (m_currentCursorShapeData.images.empty()) {\n                Log::logger->log(Log::ERR, \"BUG THIS: No fallback found for a cursor in setCursorFromName\");\n                return false;\n            }\n        }\n\n        auto buf = makeShared<CCursorBuffer>(m_currentCursorShapeData.images[0].surface, Vector2D{m_currentCursorShapeData.images[0].size, m_currentCursorShapeData.images[0].size},\n                                             Vector2D{m_currentCursorShapeData.images[0].hotspotX, m_currentCursorShapeData.images[0].hotspotY});\n        auto hotspot = Vector2D{m_currentCursorShapeData.images[0].hotspotX, m_currentCursorShapeData.images[0].hotspotY} / m_cursorScale;\n        setCursorBuffer(buf, hotspot, m_cursorScale);\n\n        int delay = 0;\n        int frame = 0;\n        if (m_currentCursorShapeData.images.size() > 1)\n            delay = m_currentCursorShapeData.images[frame].delay;\n\n        setAnimationTimer(frame, delay);\n        return true;\n    };\n\n    if (!m_hyprcursor->valid() || !*PUSEHYPRCURSOR || !setHyprCursor(name))\n        setXCursor(name);\n}\n\nvoid CCursorManager::tickAnimatedCursor() {\n    if (!m_ourBufferConnected)\n        return;\n\n    if (!m_hyprcursor->valid() && m_currentXcursor->images.size() > 1) {\n        m_currentAnimationFrame++;\n\n        if (sc<size_t>(m_currentAnimationFrame) >= m_currentXcursor->images.size())\n            m_currentAnimationFrame = 0;\n\n        float scale = std::ceil(m_cursorScale);\n        auto& icon  = m_currentXcursor->images.at(m_currentAnimationFrame);\n        auto  buf   = makeShared<CCursorBuffer>(rc<uint8_t*>(icon.pixels.data()), icon.size, icon.hotspot);\n        setCursorBuffer(buf, icon.hotspot / scale, scale);\n        setAnimationTimer(m_currentAnimationFrame, m_currentXcursor->images[m_currentAnimationFrame].delay);\n    } else if (m_currentCursorShapeData.images.size() > 1) {\n        m_currentAnimationFrame++;\n\n        if (sc<size_t>(m_currentAnimationFrame) >= m_currentCursorShapeData.images.size())\n            m_currentAnimationFrame = 0;\n\n        auto hotspot =\n            Vector2D{m_currentCursorShapeData.images[m_currentAnimationFrame].hotspotX, m_currentCursorShapeData.images[m_currentAnimationFrame].hotspotY} / m_cursorScale;\n        auto buf = makeShared<CCursorBuffer>(\n            m_currentCursorShapeData.images[m_currentAnimationFrame].surface,\n            Vector2D{m_currentCursorShapeData.images[m_currentAnimationFrame].size, m_currentCursorShapeData.images[m_currentAnimationFrame].size},\n            Vector2D{m_currentCursorShapeData.images[m_currentAnimationFrame].hotspotX, m_currentCursorShapeData.images[m_currentAnimationFrame].hotspotY});\n        setCursorBuffer(buf, hotspot, m_cursorScale);\n        setAnimationTimer(m_currentAnimationFrame, m_currentCursorShapeData.images[m_currentAnimationFrame].delay);\n    }\n}\n\nSCursorImageData CCursorManager::dataFor(const std::string& name) {\n\n    if (!m_hyprcursor->valid())\n        return {};\n\n    const auto IMAGES = m_hyprcursor->getShape(name.c_str(), m_currentStyleInfo);\n\n    if (IMAGES.images.empty())\n        return {};\n\n    return IMAGES.images[0];\n}\n\nvoid CCursorManager::setXWaylandCursor() {\n    static auto PUSEHYPRCURSOR = CConfigValue<Hyprlang::INT>(\"cursor:enable_hyprcursor\");\n    const auto  CURSOR         = dataFor(\"left_ptr\");\n    if (CURSOR.surface && *PUSEHYPRCURSOR)\n        g_pXWayland->setCursor(cairo_image_surface_get_data(CURSOR.surface), cairo_image_surface_get_stride(CURSOR.surface), {CURSOR.size, CURSOR.size},\n                               {CURSOR.hotspotX, CURSOR.hotspotY});\n    else {\n        auto  xcursor = m_xcursor->getShape(\"left_ptr\", m_size, 1);\n        auto& icon    = xcursor->images.front();\n\n        g_pXWayland->setCursor(rc<uint8_t*>(icon.pixels.data()), icon.size.x * 4, icon.size, icon.hotspot);\n    }\n}\n\nvoid CCursorManager::updateTheme() {\n    static auto PUSEHYPRCURSOR = CConfigValue<Hyprlang::INT>(\"cursor:enable_hyprcursor\");\n    float       highestScale   = 1.0;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m->m_scale > highestScale)\n            highestScale = m->m_scale;\n    }\n\n    m_cursorScale            = highestScale;\n    m_currentCursorShapeData = {};\n\n    if (*PUSEHYPRCURSOR) {\n        if (m_currentStyleInfo.size > 0 && m_hyprcursor->valid())\n            m_hyprcursor->cursorSurfaceStyleDone(m_currentStyleInfo);\n\n        m_currentStyleInfo.size = std::round(m_size * highestScale);\n\n        if (m_hyprcursor->valid())\n            m_hyprcursor->loadThemeStyle(m_currentStyleInfo);\n    }\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        m->m_forceFullFrames = 5;\n        g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_SHAPE);\n    }\n}\n\nbool CCursorManager::changeTheme(const std::string& name, const int size) {\n    static auto PUSEHYPRCURSOR = CConfigValue<Hyprlang::INT>(\"cursor:enable_hyprcursor\");\n    m_theme                    = name.empty() ? \"\" : name;\n    m_size                     = size <= 0 ? 24 : size;\n    auto xcursor_theme         = getenv(\"XCURSOR_THEME\") ? getenv(\"XCURSOR_THEME\") : \"default\";\n\n    if (*PUSEHYPRCURSOR) {\n        auto options                 = Hyprcursor::SManagerOptions();\n        options.logFn                = hcLogger;\n        options.allowDefaultFallback = false;\n        m_theme                      = name.empty() ? \"\" : name;\n        m_size                       = size;\n\n        m_hyprcursor = makeUnique<Hyprcursor::CHyprcursorManager>(m_theme.empty() ? nullptr : m_theme.c_str(), options);\n        if (!m_hyprcursor->valid()) {\n            Log::logger->log(Log::ERR, \"Hyprcursor failed loading theme \\\"{}\\\", falling back to XCursor.\", m_theme);\n            m_xcursor->loadTheme(m_theme.empty() ? xcursor_theme : m_theme, m_size, m_cursorScale);\n        }\n    } else\n        m_xcursor->loadTheme(m_theme.empty() ? xcursor_theme : m_theme, m_size, m_cursorScale);\n\n    updateTheme();\n\n    return true;\n}\n\nvoid CCursorManager::syncGsettings() {\n    m_xcursor->syncGsettings();\n}\n\nfloat CCursorManager::getScaledSize() const {\n    return m_size * m_cursorScale;\n}\n"
  },
  {
    "path": "src/managers/CursorManager.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <hyprcursor/hyprcursor.hpp>\n#include \"../includes.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../macros.hpp\"\n#include \"managers/eventLoop/EventLoopManager.hpp\"\n#include \"managers/XCursorManager.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n\nAQUAMARINE_FORWARD(IBuffer);\n\nclass CCursorBuffer : public Aquamarine::IBuffer {\n  public:\n    CCursorBuffer(cairo_surface_t* surf, const Vector2D& size, const Vector2D& hotspot);\n    CCursorBuffer(const uint8_t* pixelData, const Vector2D& size, const Vector2D& hotspot);\n    ~CCursorBuffer() = default;\n\n    virtual Aquamarine::eBufferCapability          caps();\n    virtual Aquamarine::eBufferType                type();\n    virtual void                                   update(const Hyprutils::Math::CRegion& damage);\n    virtual bool                                   isSynchronous(); // whether the updates to this buffer are synchronous, aka happen over cpu\n    virtual bool                                   good();\n    virtual Aquamarine::SSHMAttrs                  shm();\n    virtual std::tuple<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags);\n    virtual void                                   endDataPtr();\n\n  private:\n    Vector2D             m_hotspot;\n    std::vector<uint8_t> m_data;\n    size_t               m_stride = 0;\n};\n\nclass CCursorManager {\n  public:\n    CCursorManager();\n    ~CCursorManager();\n\n    SP<Aquamarine::IBuffer> getCursorBuffer();\n\n    void                    setCursorFromName(const std::string& name);\n    void                    setCursorSurface(SP<Desktop::View::CWLSurface> surf, const Vector2D& hotspot);\n    void                    setCursorBuffer(SP<CCursorBuffer> buf, const Vector2D& hotspot, const float& scale);\n    void                    setAnimationTimer(const int& frame, const int& delay);\n\n    bool                    changeTheme(const std::string& name, const int size);\n    void                    updateTheme();\n    SCursorImageData        dataFor(const std::string& name); // for xwayland\n    void                    setXWaylandCursor();\n    void                    syncGsettings();\n\n    void                    tickAnimatedCursor();\n\n    float                   getScaledSize() const;\n\n  private:\n    bool                               m_ourBufferConnected = false;\n    std::vector<SP<CCursorBuffer>>     m_cursorBuffers;\n\n    UP<Hyprcursor::CHyprcursorManager> m_hyprcursor;\n    UP<CXCursorManager>                m_xcursor;\n    SP<SXCursors>                      m_currentXcursor;\n\n    std::string                        m_theme       = \"\";\n    int                                m_size        = 0;\n    float                              m_cursorScale = 1.0;\n\n    Hyprcursor::SCursorStyleInfo       m_currentStyleInfo;\n\n    SP<CEventLoopTimer>                m_animationTimer;\n    int                                m_currentAnimationFrame = 0;\n    Hyprcursor::SCursorShapeData       m_currentCursorShapeData;\n};\n\ninline UP<CCursorManager> g_pCursorManager;\n"
  },
  {
    "path": "src/managers/DonationNagManager.cpp",
    "content": "#include \"DonationNagManager.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"VersionKeeperManager.hpp\"\n#include \"eventLoop/EventLoopManager.hpp\"\n#include \"../config/ConfigValue.hpp\"\n\n#include <chrono>\n#include <format>\n\n#include \"../helpers/fs/FsUtils.hpp\"\n\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/string/VarList.hpp>\nusing namespace Hyprutils::OS;\nusing namespace Hyprutils::String;\n\nconstexpr const char* LAST_NAG_FILE_NAME = \"lastNag\";\nconstexpr uint64_t    DAY_IN_SECONDS     = 3600ULL * 24;\nconstexpr uint64_t    MONTH_IN_SECONDS   = DAY_IN_SECONDS * 30;\n\nstruct SNagDatePoint {\n    // Counted from 1, as in Jan 1st is 1, 1\n    // No month-boundaries because I am lazy\n    uint8_t month = 0, dayStart = 0, dayEnd = 0;\n};\n\n// clang-format off\nconst std::vector<SNagDatePoint> NAG_DATE_POINTS = {\n    SNagDatePoint {\n        7, 20, 31,\n    },\n    SNagDatePoint {\n        12, 1, 28\n    },\n};\n// clang-format on\n\nCDonationNagManager::CDonationNagManager() {\n    static auto PNONAG = CConfigValue<Hyprlang::INT>(\"ecosystem:no_donation_nag\");\n\n    if (g_pVersionKeeperMgr->fired() || *PNONAG)\n        return;\n\n    const auto DATAROOT = NFsUtils::getDataHome();\n\n    if (!DATAROOT)\n        return;\n\n    const auto EPOCH = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();\n\n    uint64_t   currentMajor = 0;\n    try {\n        CVarList vl(HYPRLAND_VERSION, 0, '.');\n        currentMajor = std::stoull(vl[1]);\n    } catch (...) {\n        // ????\n        return;\n    }\n\n    auto state = getState();\n\n    if ((!state.major && currentMajor <= 48) || !state.epoch) {\n        state.major = currentMajor;\n        state.epoch = state.epoch == 0 ? EPOCH : state.epoch;\n        writeState(state);\n        return;\n    }\n\n    // don't nag if the last nag was less than a month ago. This is\n    // mostly for first-time nags, as other nags happen in specific time frames shorter than a month\n    if (EPOCH - state.epoch < MONTH_IN_SECONDS) {\n        Log::logger->log(Log::DEBUG, \"DonationNag: last nag was {} days ago, too early for a nag.\", sc<int>(std::round((EPOCH - state.epoch) / sc<double>(DAY_IN_SECONDS))));\n        return;\n    }\n\n    if (!NFsUtils::executableExistsInPath(\"hyprland-donate-screen\")) {\n        Log::logger->log(Log::ERR, \"DonationNag: executable doesn't exist, skipping.\");\n        return;\n    }\n\n    auto       tt    = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n    auto       local = *localtime(&tt);\n\n    const auto MONTH = local.tm_mon + 1;\n    const auto DAY   = local.tm_mday;\n\n    for (const auto& nagPoint : NAG_DATE_POINTS) {\n        if (MONTH != nagPoint.month)\n            continue;\n\n        if (DAY < nagPoint.dayStart || DAY > nagPoint.dayEnd)\n            continue;\n\n        Log::logger->log(Log::DEBUG, \"DonationNag: hit nag month {} days {}-{}, it's {} today, nagging\", MONTH, nagPoint.dayStart, nagPoint.dayEnd, DAY);\n\n        fire();\n\n        state.major = currentMajor;\n        state.epoch = EPOCH;\n        writeState(state);\n\n        break;\n    }\n\n    if (!m_fired)\n        Log::logger->log(Log::DEBUG, \"DonationNag: didn't hit any nagging periods, checking update\");\n\n    if (state.major < currentMajor) {\n        Log::logger->log(Log::DEBUG, \"DonationNag: hit nag for major update {} -> {}\", state.major, currentMajor);\n\n        fire();\n\n        state.major = currentMajor;\n        state.epoch = EPOCH;\n        writeState(state);\n    }\n\n    if (!m_fired)\n        Log::logger->log(Log::DEBUG, \"DonationNag: didn't hit nagging conditions\");\n}\n\nbool CDonationNagManager::fired() {\n    return m_fired;\n}\n\nvoid CDonationNagManager::fire() {\n    static const auto DATAROOT = NFsUtils::getDataHome();\n\n    m_fired = true;\n\n    g_pEventLoopManager->doLater([] {\n        CProcess proc(\"hyprland-donate-screen\", {});\n        proc.runAsync();\n    });\n}\n\nCDonationNagManager::SStateData CDonationNagManager::getState() {\n    static const auto DATAROOT = NFsUtils::getDataHome();\n    const auto        STR      = NFsUtils::readFileAsString(*DATAROOT + \"/\" + LAST_NAG_FILE_NAME);\n\n    if (!STR.has_value())\n        return {};\n\n    CVarList                        lines(*STR, 0, '\\n');\n    CDonationNagManager::SStateData state;\n\n    try {\n        state.epoch = std::stoull(lines[0]);\n        state.major = std::stoull(lines[1]);\n    } catch (...) { ; }\n\n    return state;\n}\n\nvoid CDonationNagManager::writeState(const SStateData& s) {\n    static const auto DATAROOT = NFsUtils::getDataHome();\n    NFsUtils::writeToFile(*DATAROOT + \"/\" + LAST_NAG_FILE_NAME, std::format(\"{}\\n{}\", s.epoch, s.major));\n}\n"
  },
  {
    "path": "src/managers/DonationNagManager.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n\nclass CDonationNagManager {\n  public:\n    CDonationNagManager();\n\n    // whether the donation nag was shown this boot.\n    bool fired();\n\n  private:\n    struct SStateData {\n        uint64_t epoch = 0;\n        uint64_t major = 0;\n    };\n\n    SStateData getState();\n    void       writeState(const SStateData& s);\n    void       fire();\n\n    bool       m_fired = false;\n};\n\ninline UP<CDonationNagManager> g_pDonationNagManager;"
  },
  {
    "path": "src/managers/EventManager.cpp",
    "content": "#include \"EventManager.hpp\"\n#include \"../Compositor.hpp\"\n\n#include <algorithm>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <unistd.h>\n#include <cstring>\nusing namespace Hyprutils::OS;\n\nCEventManager::CEventManager() : m_socketFD(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) {\n    if (!m_socketFD.isValid()) {\n        Log::logger->log(Log::ERR, \"Couldn't start the Hyprland Socket 2. (1) IPC will not work.\");\n        return;\n    }\n\n    sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX};\n    const auto  PATH          = g_pCompositor->m_instancePath + \"/.socket2.sock\";\n    if (PATH.length() > sizeof(SERVERADDRESS.sun_path) - 1) {\n        Log::logger->log(Log::ERR, \"Socket2 path is too long. (2) IPC will not work.\");\n        return;\n    }\n\n    strncpy(SERVERADDRESS.sun_path, PATH.c_str(), sizeof(SERVERADDRESS.sun_path) - 1);\n\n    if (bind(m_socketFD.get(), rc<sockaddr*>(&SERVERADDRESS), SUN_LEN(&SERVERADDRESS)) < 0) {\n        Log::logger->log(Log::ERR, \"Couldn't bind the Hyprland Socket 2. (3) IPC will not work.\");\n        return;\n    }\n\n    // 10 max queued.\n    if (listen(m_socketFD.get(), 10) < 0) {\n        Log::logger->log(Log::ERR, \"Couldn't listen on the Hyprland Socket 2. (4) IPC will not work.\");\n        return;\n    }\n\n    m_eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_socketFD.get(), WL_EVENT_READABLE, onClientEvent, nullptr);\n}\n\nCEventManager::~CEventManager() {\n    for (const auto& client : m_clients) {\n        wl_event_source_remove(client.eventSource);\n    }\n\n    if (m_eventSource != nullptr)\n        wl_event_source_remove(m_eventSource);\n}\n\nint CEventManager::onServerEvent(int fd, uint32_t mask, void* data) {\n    return g_pEventManager->onClientEvent(fd, mask);\n}\n\nint CEventManager::onClientEvent(int fd, uint32_t mask, void* data) {\n    return g_pEventManager->onServerEvent(fd, mask);\n}\n\nint CEventManager::onServerEvent(int fd, uint32_t mask) {\n    if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) {\n        Log::logger->log(Log::ERR, \"Socket2 hangup?? IPC broke\");\n\n        wl_event_source_remove(m_eventSource);\n        m_eventSource = nullptr;\n        m_socketFD.reset();\n\n        return 0;\n    }\n\n    sockaddr_in     clientAddress;\n    socklen_t       clientSize = sizeof(clientAddress);\n    CFileDescriptor ACCEPTEDCONNECTION{accept4(m_socketFD.get(), rc<sockaddr*>(&clientAddress), &clientSize, SOCK_CLOEXEC | SOCK_NONBLOCK)};\n    if (!ACCEPTEDCONNECTION.isValid()) {\n        if (errno != EAGAIN) {\n            Log::logger->log(Log::ERR, \"Socket2 failed receiving connection, errno: {}\", errno);\n            wl_event_source_remove(m_eventSource);\n            m_eventSource = nullptr;\n            m_socketFD.reset();\n        }\n\n        return 0;\n    }\n\n    Log::logger->log(Log::DEBUG, \"Socket2 accepted a new client at FD {}\", ACCEPTEDCONNECTION.get());\n\n    // add to event loop so we can close it when we need to\n    auto* eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, ACCEPTEDCONNECTION.get(), 0, onServerEvent, nullptr);\n    m_clients.emplace_back(SClient{\n        std::move(ACCEPTEDCONNECTION),\n        {},\n        eventSource,\n    });\n\n    return 0;\n}\n\nint CEventManager::onClientEvent(int fd, uint32_t mask) {\n    if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) {\n        Log::logger->log(Log::DEBUG, \"Socket2 fd {} hung up\", fd);\n        removeClientByFD(fd);\n        return 0;\n    }\n\n    if (mask & WL_EVENT_WRITABLE) {\n        const auto CLIENTIT = findClientByFD(fd);\n\n        // send all queued events\n        while (!CLIENTIT->events.empty()) {\n            const auto& event = CLIENTIT->events.front();\n            if (write(CLIENTIT->fd.get(), event->c_str(), event->length()) < 0)\n                break;\n\n            CLIENTIT->events.erase(CLIENTIT->events.begin());\n        }\n\n        // stop polling when we sent all events\n        if (CLIENTIT->events.empty())\n            wl_event_source_fd_update(CLIENTIT->eventSource, 0);\n    }\n\n    return 0;\n}\n\nstd::vector<CEventManager::SClient>::iterator CEventManager::findClientByFD(int fd) {\n    return std::ranges::find_if(m_clients, [fd](const auto& client) { return client.fd.get() == fd; });\n}\n\nstd::vector<CEventManager::SClient>::iterator CEventManager::removeClientByFD(int fd) {\n    const auto CLIENTIT = findClientByFD(fd);\n    wl_event_source_remove(CLIENTIT->eventSource);\n\n    return m_clients.erase(CLIENTIT);\n}\n\nstd::string CEventManager::formatEvent(const SHyprIPCEvent& event) const {\n    std::string_view data        = event.data;\n    auto             eventString = std::format(\"{}>>{}\\n\", event.event, data.substr(0, 1024));\n    std::replace(eventString.begin() + event.event.length() + 2, eventString.end() - 1, '\\n', ' ');\n    return eventString;\n}\n\nvoid CEventManager::postEvent(const SHyprIPCEvent& event) {\n    if (g_pCompositor->m_isShuttingDown) {\n        Log::logger->log(Log::WARN, \"Suppressed (shutting down) event of type {}, content: {}\", event.event, event.data);\n        return;\n    }\n\n    const size_t MAX_QUEUED_EVENTS = 64;\n    auto         sharedEvent       = makeShared<std::string>(formatEvent(event));\n    for (auto it = m_clients.begin(); it != m_clients.end();) {\n        // try to send the event immediately if the queue is empty\n        const auto QUEUESIZE = it->events.size();\n        if (QUEUESIZE > 0 || write(it->fd.get(), sharedEvent->c_str(), sharedEvent->length()) < 0) {\n            if (QUEUESIZE >= MAX_QUEUED_EVENTS) {\n                // too many events queued, remove the client\n                Log::logger->log(Log::ERR, \"Socket2 fd {} overflowed event queue, removing\", it->fd.get());\n                it = removeClientByFD(it->fd.get());\n                continue;\n            }\n\n            // queue it to send later if failed\n            it->events.push_back(sharedEvent);\n\n            // poll for write if queue was empty\n            if (QUEUESIZE == 0)\n                wl_event_source_fd_update(it->eventSource, WL_EVENT_WRITABLE);\n        }\n\n        ++it;\n    }\n}\n"
  },
  {
    "path": "src/managers/EventManager.hpp",
    "content": "#pragma once\n#include <vector>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include \"../defines.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n\nstruct SHyprIPCEvent {\n    std::string event;\n    std::string data;\n};\n\nclass CEventManager {\n  public:\n    CEventManager();\n    ~CEventManager();\n\n    void postEvent(const SHyprIPCEvent& event);\n\n  private:\n    std::string formatEvent(const SHyprIPCEvent& event) const;\n\n    static int  onServerEvent(int fd, uint32_t mask, void* data);\n    static int  onClientEvent(int fd, uint32_t mask, void* data);\n\n    int         onServerEvent(int fd, uint32_t mask);\n    int         onClientEvent(int fd, uint32_t mask);\n\n    struct SClient {\n        Hyprutils::OS::CFileDescriptor fd;\n        std::vector<SP<std::string>>   events;\n        wl_event_source*               eventSource = nullptr;\n    };\n\n    std::vector<SClient>::iterator findClientByFD(int fd);\n    std::vector<SClient>::iterator removeClientByFD(int fd);\n\n  private:\n    Hyprutils::OS::CFileDescriptor m_socketFD;\n    wl_event_source*               m_eventSource = nullptr;\n\n    std::vector<SClient>           m_clients;\n};\n\ninline UP<CEventManager> g_pEventManager;\n"
  },
  {
    "path": "src/managers/KeybindManager.cpp",
    "content": "#include \"../config/ConfigValue.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../desktop/history/WindowHistoryTracker.hpp\"\n#include \"../desktop/history/WorkspaceHistoryTracker.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../protocols/ShortcutsInhibit.hpp\"\n#include \"../protocols/GlobalShortcuts.hpp\"\n#include \"../protocols/IdleNotify.hpp\"\n#include \"../protocols/core/DataDevice.hpp\"\n#include \"../render/decorations/CHyprGroupBarDecoration.hpp\"\n#include \"KeybindManager.hpp\"\n#include \"PointerManager.hpp\"\n#include \"Compositor.hpp\"\n#include \"TokenManager.hpp\"\n#include \"eventLoop/EventLoopManager.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/animation/DesktopAnimationManager.hpp\"\n#include \"../managers/EventManager.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../hyprerror/HyprError.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../desktop/rule/windowRule/WindowRule.hpp\"\n#include \"../desktop/rule/Engine.hpp\"\n#include \"../desktop/view/Group.hpp\"\n#include \"../layout/LayoutManager.hpp\"\n#include \"../layout/target/WindowTarget.hpp\"\n#include \"../layout/space/Space.hpp\"\n#include \"../layout/algorithm/Algorithm.hpp\"\n#include \"../layout/algorithm/tiled/master/MasterAlgorithm.hpp\"\n#include \"../layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp\"\n#include \"../event/EventBus.hpp\"\n\n#include <optional>\n#include <iterator>\n#include <string>\n#include <string_view>\n#include <cstring>\n\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/string/ConstVarList.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::OS;\n\n#include <sys/ioctl.h>\n#include <fcntl.h>\n#include <vector>\n#if defined(__linux__)\n#include <linux/vt.h>\n#elif defined(__NetBSD__) || defined(__OpenBSD__)\n#include <dev/wscons/wsdisplay_usl_io.h>\n#elif defined(__DragonFly__) || defined(__FreeBSD__)\n#include <sys/consio.h>\n#endif\n\nstatic std::vector<std::pair<std::string, std::string>> getHyprlandLaunchEnv(PHLWORKSPACE pInitialWorkspace) {\n    static auto PINITIALWSTRACKING = CConfigValue<Hyprlang::INT>(\"misc:initial_workspace_tracking\");\n\n    if (!*PINITIALWSTRACKING || g_pConfigManager->m_isLaunchingExecOnce)\n        return {};\n\n    const auto PMONITOR = Desktop::focusState()->monitor();\n    if (!PMONITOR || !PMONITOR->m_activeWorkspace)\n        return {};\n\n    std::vector<std::pair<std::string, std::string>> result;\n\n    if (!pInitialWorkspace) {\n        if (PMONITOR->m_activeSpecialWorkspace)\n            pInitialWorkspace = PMONITOR->m_activeSpecialWorkspace;\n        else\n            pInitialWorkspace = PMONITOR->m_activeWorkspace;\n    }\n\n    result.push_back(std::make_pair<>(\"HL_INITIAL_WORKSPACE_TOKEN\",\n                                      g_pTokenManager->registerNewToken(Desktop::View::SInitialWorkspaceToken{{}, pInitialWorkspace->getConfigName()}, std::chrono::months(1337))));\n\n    return result;\n}\n\nCKeybindManager::CKeybindManager() {\n    // initialize all dispatchers\n\n    m_dispatchers[\"exec\"]                           = spawn;\n    m_dispatchers[\"execr\"]                          = spawnRaw;\n    m_dispatchers[\"killactive\"]                     = closeActive;\n    m_dispatchers[\"forcekillactive\"]                = killActive;\n    m_dispatchers[\"closewindow\"]                    = closeWindow;\n    m_dispatchers[\"killwindow\"]                     = killWindow;\n    m_dispatchers[\"signal\"]                         = signalActive;\n    m_dispatchers[\"signalwindow\"]                   = signalWindow;\n    m_dispatchers[\"togglefloating\"]                 = toggleActiveFloating;\n    m_dispatchers[\"setfloating\"]                    = setActiveFloating;\n    m_dispatchers[\"settiled\"]                       = setActiveTiled;\n    m_dispatchers[\"workspace\"]                      = changeworkspace;\n    m_dispatchers[\"renameworkspace\"]                = renameWorkspace;\n    m_dispatchers[\"fullscreen\"]                     = fullscreenActive;\n    m_dispatchers[\"fullscreenstate\"]                = fullscreenStateActive;\n    m_dispatchers[\"movetoworkspace\"]                = moveActiveToWorkspace;\n    m_dispatchers[\"movetoworkspacesilent\"]          = moveActiveToWorkspaceSilent;\n    m_dispatchers[\"pseudo\"]                         = toggleActivePseudo;\n    m_dispatchers[\"movefocus\"]                      = moveFocusTo;\n    m_dispatchers[\"movewindow\"]                     = moveActiveTo;\n    m_dispatchers[\"swapwindow\"]                     = swapActive;\n    m_dispatchers[\"centerwindow\"]                   = centerWindow;\n    m_dispatchers[\"togglegroup\"]                    = toggleGroup;\n    m_dispatchers[\"changegroupactive\"]              = changeGroupActive;\n    m_dispatchers[\"movegroupwindow\"]                = moveGroupWindow;\n    m_dispatchers[\"focusmonitor\"]                   = focusMonitor;\n    m_dispatchers[\"movecursortocorner\"]             = moveCursorToCorner;\n    m_dispatchers[\"movecursor\"]                     = moveCursor;\n    m_dispatchers[\"workspaceopt\"]                   = workspaceOpt;\n    m_dispatchers[\"exit\"]                           = exitHyprland;\n    m_dispatchers[\"movecurrentworkspacetomonitor\"]  = moveCurrentWorkspaceToMonitor;\n    m_dispatchers[\"focusworkspaceoncurrentmonitor\"] = focusWorkspaceOnCurrentMonitor;\n    m_dispatchers[\"moveworkspacetomonitor\"]         = moveWorkspaceToMonitor;\n    m_dispatchers[\"togglespecialworkspace\"]         = toggleSpecialWorkspace;\n    m_dispatchers[\"forcerendererreload\"]            = forceRendererReload;\n    m_dispatchers[\"resizeactive\"]                   = resizeActive;\n    m_dispatchers[\"moveactive\"]                     = moveActive;\n    m_dispatchers[\"cyclenext\"]                      = circleNext;\n    m_dispatchers[\"focuswindowbyclass\"]             = focusWindow;\n    m_dispatchers[\"focuswindow\"]                    = focusWindow;\n    m_dispatchers[\"tagwindow\"]                      = tagWindow;\n    m_dispatchers[\"toggleswallow\"]                  = toggleSwallow;\n    m_dispatchers[\"submap\"]                         = setSubmap;\n    m_dispatchers[\"pass\"]                           = pass;\n    m_dispatchers[\"sendshortcut\"]                   = sendshortcut;\n    m_dispatchers[\"sendkeystate\"]                   = sendkeystate;\n    m_dispatchers[\"layoutmsg\"]                      = layoutmsg;\n    m_dispatchers[\"dpms\"]                           = dpms;\n    m_dispatchers[\"movewindowpixel\"]                = moveWindow;\n    m_dispatchers[\"resizewindowpixel\"]              = resizeWindow;\n    m_dispatchers[\"swapnext\"]                       = swapnext;\n    m_dispatchers[\"swapactiveworkspaces\"]           = swapActiveWorkspaces;\n    m_dispatchers[\"pin\"]                            = pinActive;\n    m_dispatchers[\"mouse\"]                          = mouse;\n    m_dispatchers[\"bringactivetotop\"]               = bringActiveToTop;\n    m_dispatchers[\"alterzorder\"]                    = alterZOrder;\n    m_dispatchers[\"focusurgentorlast\"]              = focusUrgentOrLast;\n    m_dispatchers[\"focuscurrentorlast\"]             = focusCurrentOrLast;\n    m_dispatchers[\"lockgroups\"]                     = lockGroups;\n    m_dispatchers[\"lockactivegroup\"]                = lockActiveGroup;\n    m_dispatchers[\"moveintogroup\"]                  = moveIntoGroup;\n    m_dispatchers[\"moveoutofgroup\"]                 = moveOutOfGroup;\n    m_dispatchers[\"movewindoworgroup\"]              = moveWindowOrGroup;\n    m_dispatchers[\"setignoregrouplock\"]             = setIgnoreGroupLock;\n    m_dispatchers[\"denywindowfromgroup\"]            = denyWindowFromGroup;\n    m_dispatchers[\"event\"]                          = event;\n    m_dispatchers[\"global\"]                         = global;\n    m_dispatchers[\"setprop\"]                        = setProp;\n    m_dispatchers[\"forceidle\"]                      = forceIdle;\n\n    m_scrollTimer.reset();\n\n    m_longPressTimer = makeShared<CEventLoopTimer>(\n        std::nullopt,\n        [this](SP<CEventLoopTimer> self, void* data) {\n            if (!m_lastLongPressKeybind || g_pSeatManager->m_keyboard.expired())\n                return;\n\n            const auto PACTIVEKEEB = g_pSeatManager->m_keyboard.lock();\n            if (!PACTIVEKEEB->m_allowBinds)\n                return;\n\n            const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(m_lastLongPressKeybind->handler);\n\n            Log::logger->log(Log::DEBUG, \"Long press timeout passed, calling dispatcher.\");\n            DISPATCHER->second(m_lastLongPressKeybind->arg);\n        },\n        nullptr);\n\n    m_repeatKeyTimer = makeShared<CEventLoopTimer>(\n        std::nullopt,\n        [this](SP<CEventLoopTimer> self, void* data) {\n            if (m_activeKeybinds.empty() || g_pSeatManager->m_keyboard.expired())\n                return;\n\n            const auto PACTIVEKEEB = g_pSeatManager->m_keyboard.lock();\n            if (!PACTIVEKEEB->m_allowBinds)\n                return;\n\n            for (const auto& k : m_activeKeybinds) {\n                const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(k->handler);\n\n                Log::logger->log(Log::DEBUG, \"Keybind repeat triggered, calling dispatcher.\");\n                DISPATCHER->second(k->arg);\n            }\n\n            self->updateTimeout(std::chrono::milliseconds(1000 / m_repeatKeyRate));\n        },\n        nullptr);\n\n    // null in --verify-config mode\n    if (g_pEventLoopManager) {\n        g_pEventLoopManager->addTimer(m_longPressTimer);\n        g_pEventLoopManager->addTimer(m_repeatKeyTimer);\n    }\n\n    static auto P = Event::bus()->m_events.config.reloaded.listen([this] {\n        m_activeKeybinds.clear();\n        m_lastLongPressKeybind.reset();\n        m_pressedSpecialBinds.clear();\n    });\n}\n\nCKeybindManager::~CKeybindManager() {\n    if (m_xkbTranslationState)\n        xkb_state_unref(m_xkbTranslationState);\n    if (m_longPressTimer && g_pEventLoopManager) {\n        g_pEventLoopManager->removeTimer(m_longPressTimer);\n        m_longPressTimer.reset();\n    }\n    if (m_repeatKeyTimer && g_pEventLoopManager) {\n        g_pEventLoopManager->removeTimer(m_repeatKeyTimer);\n        m_repeatKeyTimer.reset();\n    }\n}\n\nvoid CKeybindManager::addKeybind(SKeybind kb) {\n    m_keybinds.emplace_back(makeShared<SKeybind>(kb));\n\n    m_activeKeybinds.clear();\n    m_lastLongPressKeybind.reset();\n}\n\nvoid CKeybindManager::removeKeybind(uint32_t mod, const SParsedKey& key) {\n    std::erase_if(m_keybinds, [&mod, &key](const auto& el) { return el->modmask == mod && el->key == key.key && el->keycode == key.keycode && el->catchAll == key.catchAll; });\n\n    m_activeKeybinds.clear();\n    m_lastLongPressKeybind.reset();\n}\n\nuint32_t CKeybindManager::stringToModMask(std::string mods) {\n    uint32_t modMask = 0;\n    std::ranges::transform(mods, mods.begin(), ::toupper);\n    if (mods.contains(\"SHIFT\"))\n        modMask |= HL_MODIFIER_SHIFT;\n    if (mods.contains(\"CAPS\"))\n        modMask |= HL_MODIFIER_CAPS;\n    if (mods.contains(\"CTRL\") || mods.contains(\"CONTROL\"))\n        modMask |= HL_MODIFIER_CTRL;\n    if (mods.contains(\"ALT\") || mods.contains(\"MOD1\"))\n        modMask |= HL_MODIFIER_ALT;\n    if (mods.contains(\"MOD2\"))\n        modMask |= HL_MODIFIER_MOD2;\n    if (mods.contains(\"MOD3\"))\n        modMask |= HL_MODIFIER_MOD3;\n    if (mods.contains(\"SUPER\") || mods.contains(\"WIN\") || mods.contains(\"LOGO\") || mods.contains(\"MOD4\") || mods.contains(\"META\"))\n        modMask |= HL_MODIFIER_META;\n    if (mods.contains(\"MOD5\"))\n        modMask |= HL_MODIFIER_MOD5;\n\n    return modMask;\n}\n\nuint32_t CKeybindManager::keycodeToModifier(xkb_keycode_t keycode) {\n    if (keycode == 0)\n        return 0;\n\n    switch (keycode - 8) {\n        case KEY_LEFTMETA: return HL_MODIFIER_META;\n        case KEY_RIGHTMETA: return HL_MODIFIER_META;\n        case KEY_LEFTSHIFT: return HL_MODIFIER_SHIFT;\n        case KEY_RIGHTSHIFT: return HL_MODIFIER_SHIFT;\n        case KEY_LEFTCTRL: return HL_MODIFIER_CTRL;\n        case KEY_RIGHTCTRL: return HL_MODIFIER_CTRL;\n        case KEY_LEFTALT: return HL_MODIFIER_ALT;\n        case KEY_RIGHTALT: return HL_MODIFIER_ALT;\n        case KEY_CAPSLOCK: return HL_MODIFIER_CAPS;\n        case KEY_NUMLOCK: return HL_MODIFIER_MOD2;\n        default: return 0;\n    }\n}\n\nvoid CKeybindManager::updateXKBTranslationState() {\n    if (m_xkbTranslationState) {\n        xkb_state_unref(m_xkbTranslationState);\n\n        m_xkbTranslationState = nullptr;\n    }\n\n    static auto       PFILEPATH = CConfigValue<std::string>(\"input:kb_file\");\n    static auto       PRULES    = CConfigValue<std::string>(\"input:kb_rules\");\n    static auto       PMODEL    = CConfigValue<std::string>(\"input:kb_model\");\n    static auto       PLAYOUT   = CConfigValue<std::string>(\"input:kb_layout\");\n    static auto       PVARIANT  = CConfigValue<std::string>(\"input:kb_variant\");\n    static auto       POPTIONS  = CConfigValue<std::string>(\"input:kb_options\");\n\n    const std::string FILEPATH = std::string{*PFILEPATH} == STRVAL_EMPTY ? \"\" : *PFILEPATH;\n    const std::string RULES    = std::string{*PRULES} == STRVAL_EMPTY ? \"\" : *PRULES;\n    const std::string MODEL    = std::string{*PMODEL} == STRVAL_EMPTY ? \"\" : *PMODEL;\n    const std::string LAYOUT   = std::string{*PLAYOUT} == STRVAL_EMPTY ? \"\" : *PLAYOUT;\n    const std::string VARIANT  = std::string{*PVARIANT} == STRVAL_EMPTY ? \"\" : *PVARIANT;\n    const std::string OPTIONS  = std::string{*POPTIONS} == STRVAL_EMPTY ? \"\" : *POPTIONS;\n\n    xkb_rule_names    rules      = {.rules = RULES.c_str(), .model = MODEL.c_str(), .layout = LAYOUT.c_str(), .variant = VARIANT.c_str(), .options = OPTIONS.c_str()};\n    const auto        PCONTEXT   = xkb_context_new(XKB_CONTEXT_NO_FLAGS);\n    FILE* const       KEYMAPFILE = FILEPATH.empty() ? nullptr : fopen(absolutePath(FILEPATH, g_pConfigManager->m_configCurrentPath).c_str(), \"r\");\n\n    auto              PKEYMAP = KEYMAPFILE ? xkb_keymap_new_from_file(PCONTEXT, KEYMAPFILE, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS) :\n                                             xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n    if (KEYMAPFILE)\n        fclose(KEYMAPFILE);\n\n    if (!PKEYMAP) {\n        g_pHyprError->queueCreate(\"[Runtime Error] Invalid keyboard layout passed. ( rules: \" + RULES + \", model: \" + MODEL + \", variant: \" + VARIANT + \", options: \" + OPTIONS +\n                                      \", layout: \" + LAYOUT + \" )\",\n                                  CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0));\n\n        Log::logger->log(Log::ERR, \"[XKBTranslationState] Keyboard layout {} with variant {} (rules: {}, model: {}, options: {}) couldn't have been loaded.\", rules.layout,\n                         rules.variant, rules.rules, rules.model, rules.options);\n        memset(&rules, 0, sizeof(rules));\n\n        PKEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n    }\n\n    xkb_context_unref(PCONTEXT);\n    m_xkbTranslationState = xkb_state_new(PKEYMAP);\n    xkb_keymap_unref(PKEYMAP);\n}\n\nbool CKeybindManager::ensureMouseBindState() {\n    if (!g_layoutManager->dragController()->target())\n        return false;\n\n    if (g_layoutManager->dragController()->target()) {\n        changeMouseBindMode(MBIND_INVALID);\n        return true;\n    }\n\n    return false;\n}\n\nstatic void updateRelativeCursorCoords() {\n    static auto PNOWARPS = CConfigValue<Hyprlang::INT>(\"cursor:no_warps\");\n\n    if (*PNOWARPS)\n        return;\n\n    if (Desktop::focusState()->window())\n        Desktop::focusState()->window()->m_relativeCursorCoordsOnLastWarp = g_pInputManager->getMouseCoordsInternal() - Desktop::focusState()->window()->m_position;\n}\n\nbool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) {\n    if (!monitor)\n        return false;\n\n    const auto LASTMONITOR = Desktop::focusState()->monitor();\n    if (!LASTMONITOR)\n        return false;\n    if (LASTMONITOR == monitor) {\n        Log::logger->log(Log::DEBUG, \"Tried to move to active monitor\");\n        return false;\n    }\n\n    static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n    static auto PNOWARPS     = CConfigValue<Hyprlang::INT>(\"cursor:no_warps\");\n\n    const auto  PWORKSPACE        = Desktop::focusState()->monitor()->m_activeWorkspace;\n    const auto  PNEWMAINWORKSPACE = monitor->m_activeWorkspace;\n\n    g_pInputManager->unconstrainMouse();\n\n    const auto PNEWWORKSPACE = monitor->m_activeSpecialWorkspace ? monitor->m_activeSpecialWorkspace : PNEWMAINWORKSPACE;\n\n    const auto PNEWWINDOW = PNEWWORKSPACE->getLastFocusedWindow();\n    if (PNEWWINDOW) {\n        updateRelativeCursorCoords();\n        Desktop::focusState()->fullWindowFocus(PNEWWINDOW, Desktop::FOCUS_REASON_KEYBIND);\n        PNEWWINDOW->warpCursor();\n\n        if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) {\n            g_pInputManager->m_forcedFocus = PNEWWINDOW;\n            g_pInputManager->simulateMouseMovement();\n            g_pInputManager->m_forcedFocus.reset();\n        }\n    } else {\n        Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND);\n        g_pCompositor->warpCursorTo(monitor->middle());\n    }\n    Desktop::focusState()->rawMonitorFocus(monitor);\n\n    return true;\n}\n\nvoid CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle) {\n    static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n    static auto PNOWARPS     = CConfigValue<Hyprlang::INT>(\"cursor:no_warps\");\n\n    const auto  PLASTWINDOW = Desktop::focusState()->window();\n\n    if (PWINDOWTOCHANGETO == PLASTWINDOW || !PWINDOWTOCHANGETO)\n        return;\n\n    // remove constraints\n    g_pInputManager->unconstrainMouse();\n\n    if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen())\n        Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle);\n    else {\n        updateRelativeCursorCoords();\n        Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle);\n        PWINDOWTOCHANGETO->warpCursor();\n\n        // Move mouse focus to the new window if required by current follow_mouse and warp modes\n        if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) {\n            g_pInputManager->m_forcedFocus = PWINDOWTOCHANGETO;\n            g_pInputManager->simulateMouseMovement();\n            g_pInputManager->m_forcedFocus.reset();\n        }\n\n        if (PLASTWINDOW && PLASTWINDOW->m_monitor != PWINDOWTOCHANGETO->m_monitor) {\n            // event\n            const auto PNEWMON = PWINDOWTOCHANGETO->m_monitor.lock();\n\n            Desktop::focusState()->rawMonitorFocus(PNEWMON);\n        }\n    }\n};\n\nbool CKeybindManager::onKeyEvent(std::any event, SP<IKeyboard> pKeyboard) {\n    if (!g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState) {\n        m_pressedKeys.clear();\n        return true;\n    }\n\n    if (!pKeyboard->m_allowBinds)\n        return true;\n\n    if (!m_xkbTranslationState) {\n        Log::logger->log(Log::ERR, \"BUG THIS: m_pXKBTranslationState nullptr!\");\n        updateXKBTranslationState();\n\n        if (!m_xkbTranslationState)\n            return true;\n    }\n\n    auto               e = std::any_cast<IKeyboard::SKeyEvent>(event);\n\n    const auto         KEYCODE = e.keycode + 8; // Because to xkbcommon it's +8 from libinput\n\n    const xkb_keysym_t keysym         = xkb_state_key_get_one_sym(pKeyboard->m_resolveBindsBySym ? pKeyboard->m_xkbSymState : m_xkbTranslationState, KEYCODE);\n    const xkb_keysym_t internalKeysym = xkb_state_key_get_one_sym(pKeyboard->m_xkbState, KEYCODE);\n\n    if (keysym == XKB_KEY_Escape || internalKeysym == XKB_KEY_Escape)\n        PROTO::data->abortDndIfPresent();\n\n    // handleInternalKeybinds returns true when the key should be suppressed,\n    // while this function returns true when the key event should be sent\n    if (handleInternalKeybinds(internalKeysym))\n        return false;\n\n    const auto MODS = g_pInputManager->getModsFromAllKBs();\n\n    m_timeLastMs    = e.timeMs;\n    m_lastCode      = KEYCODE;\n    m_lastMouseCode = 0;\n\n    bool       mouseBindWasActive = ensureMouseBindState();\n\n    const auto KEY = SPressedKeyWithMods{\n        .keysym             = keysym,\n        .keycode            = KEYCODE,\n        .modmaskAtPressTime = MODS,\n        .sent               = true,\n        .submapAtPress      = m_currentSelectedSubmap,\n        .mousePosAtPress    = g_pInputManager->getMouseCoordsInternal(),\n    };\n\n    m_activeKeybinds.clear();\n\n    m_lastLongPressKeybind.reset();\n\n    bool suppressEvent = false;\n    if (e.state == WL_KEYBOARD_KEY_STATE_PRESSED) {\n\n        m_pressedKeys.push_back(KEY);\n\n        suppressEvent = !handleKeybinds(MODS, KEY, true, pKeyboard, pKeyboard).passEvent;\n\n        if (suppressEvent)\n            shadowKeybinds(keysym, KEYCODE);\n\n        m_pressedKeys.back().sent = !suppressEvent;\n    } else { // key release\n\n        bool foundInPressedKeys = false;\n        for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end();) {\n            if (it->keycode == KEYCODE) {\n                handleKeybinds(MODS, *it, false, pKeyboard, pKeyboard);\n                foundInPressedKeys = true;\n                suppressEvent      = !it->sent;\n                it                 = m_pressedKeys.erase(it);\n            } else {\n                ++it;\n            }\n        }\n        if (!foundInPressedKeys) {\n            Log::logger->log(Log::ERR, \"BUG THIS: key not found in m_dPressedKeys\");\n            // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy\n            suppressEvent = !handleKeybinds(MODS, KEY, false, pKeyboard, pKeyboard).passEvent;\n        }\n\n        shadowKeybinds();\n    }\n\n    return !suppressEvent && !mouseBindWasActive;\n}\n\nbool CKeybindManager::onAxisEvent(const IPointer::SAxisEvent& e, SP<IPointer> pointer) {\n    const auto  MODS = g_pInputManager->getModsFromAllKBs();\n\n    static auto PDELAY = CConfigValue<Hyprlang::INT>(\"binds:scroll_event_delay\");\n\n    if (m_scrollTimer.getMillis() < *PDELAY)\n        return true; // timer hasn't passed yet!\n\n    m_scrollTimer.reset();\n\n    m_activeKeybinds.clear();\n\n    bool found = false;\n    if (e.source == WL_POINTER_AXIS_SOURCE_WHEEL && e.axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {\n        if (e.delta < 0)\n            found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = \"mouse_down\"}, true, nullptr, pointer).passEvent;\n        else\n            found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = \"mouse_up\"}, true, nullptr, pointer).passEvent;\n    } else if (e.source == WL_POINTER_AXIS_SOURCE_WHEEL && e.axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {\n        if (e.delta < 0)\n            found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = \"mouse_left\"}, true, nullptr, pointer).passEvent;\n        else\n            found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = \"mouse_right\"}, true, nullptr, pointer).passEvent;\n    }\n\n    if (found)\n        shadowKeybinds();\n\n    return !found;\n}\n\nbool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e, SP<IPointer> mouse) {\n    const auto MODS = g_pInputManager->getModsFromAllKBs();\n\n    bool       suppressEvent = false;\n\n    m_lastMouseCode = e.button;\n    m_lastCode      = 0;\n    m_timeLastMs    = e.timeMs;\n\n    bool       mouseBindWasActive = ensureMouseBindState();\n\n    const auto KEY_NAME = \"mouse:\" + std::to_string(e.button);\n\n    const auto KEY = SPressedKeyWithMods{\n        .keyName            = KEY_NAME,\n        .modmaskAtPressTime = MODS,\n        .mousePosAtPress    = g_pInputManager->getMouseCoordsInternal(),\n    };\n\n    m_activeKeybinds.clear();\n\n    if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) {\n        m_pressedKeys.push_back(KEY);\n\n        suppressEvent = !handleKeybinds(MODS, KEY, true, nullptr, mouse).passEvent;\n\n        if (suppressEvent)\n            shadowKeybinds();\n\n        m_pressedKeys.back().sent = !suppressEvent;\n    } else {\n        bool foundInPressedKeys = false;\n        for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end();) {\n            if (it->keyName == KEY_NAME) {\n                suppressEvent      = !handleKeybinds(MODS, *it, false, nullptr, mouse).passEvent;\n                foundInPressedKeys = true;\n                suppressEvent      = !it->sent;\n                it                 = m_pressedKeys.erase(it);\n            } else {\n                ++it;\n            }\n        }\n        if (!foundInPressedKeys) {\n            Log::logger->log(Log::ERR, \"BUG THIS: key not found in m_dPressedKeys (2)\");\n            // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy\n            suppressEvent = !handleKeybinds(MODS, KEY, false, nullptr, mouse).passEvent;\n        }\n\n        shadowKeybinds();\n    }\n\n    return !suppressEvent && !mouseBindWasActive;\n}\n\nvoid CKeybindManager::resizeWithBorder(const IPointer::SButtonEvent& e) {\n    changeMouseBindMode(e.state == WL_POINTER_BUTTON_STATE_PRESSED ? MBIND_RESIZE : MBIND_INVALID);\n}\n\nvoid CKeybindManager::onSwitchEvent(const std::string& switchName) {\n    handleKeybinds(0, SPressedKeyWithMods{.keyName = \"switch:\" + switchName}, true, nullptr, nullptr);\n}\n\nvoid CKeybindManager::onSwitchOnEvent(const std::string& switchName) {\n    handleKeybinds(0, SPressedKeyWithMods{.keyName = \"switch:on:\" + switchName}, true, nullptr, nullptr);\n}\n\nvoid CKeybindManager::onSwitchOffEvent(const std::string& switchName) {\n    handleKeybinds(0, SPressedKeyWithMods{.keyName = \"switch:off:\" + switchName}, true, nullptr, nullptr);\n}\n\neMultiKeyCase CKeybindManager::mkKeysymSetMatches(const std::set<xkb_keysym_t> keybindKeysyms, const std::set<xkb_keysym_t> pressedKeysyms) {\n    // Returns whether two sets of keysyms are equal, partially equal, or not\n    // matching. (Partially matching means that pressed is a subset of bound)\n\n    std::set<xkb_keysym_t> boundKeysNotPressed;\n    std::set<xkb_keysym_t> pressedKeysNotBound;\n\n    std::ranges::set_difference(keybindKeysyms, pressedKeysyms, std::inserter(boundKeysNotPressed, boundKeysNotPressed.begin()));\n    std::ranges::set_difference(pressedKeysyms, keybindKeysyms, std::inserter(pressedKeysNotBound, pressedKeysNotBound.begin()));\n\n    if (boundKeysNotPressed.empty() && pressedKeysNotBound.empty())\n        return MK_FULL_MATCH;\n\n    if (!boundKeysNotPressed.empty() && pressedKeysNotBound.empty())\n        return MK_PARTIAL_MATCH;\n\n    return MK_NO_MATCH;\n}\n\neMultiKeyCase CKeybindManager::mkBindMatches(const SP<SKeybind> keybind) {\n    if (mkKeysymSetMatches(keybind->sMkMods, m_mkMods) != MK_FULL_MATCH)\n        return MK_NO_MATCH;\n\n    return mkKeysymSetMatches(keybind->sMkKeys, m_mkKeys);\n}\n\nSSubmap CKeybindManager::getCurrentSubmap() {\n    return m_currentSelectedSubmap;\n}\n\nSDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed, SP<IKeyboard> keyboard, SP<IHID> device) {\n    static auto     PDISABLEINHIBIT = CConfigValue<Hyprlang::INT>(\"binds:disable_keybind_grabbing\");\n    static auto     PDRAGTHRESHOLD  = CConfigValue<Hyprlang::INT>(\"binds:drag_threshold\");\n\n    bool            found = false;\n    SDispatchResult res;\n\n    // Skip keysym tracking for events with no keysym (e.g., scroll wheel events).\n    // Scroll events have keysym=0 and are always \"pressed\" (never released),\n    // so without this check, 0 gets inserted into m_mkKeys and never removed,\n    // breaking multi-key binds (binds flag 's'). See issue #8699.\n    if (key.keysym != 0) {\n        if (pressed) {\n            if (keycodeToModifier(key.keycode))\n                m_mkMods.insert(key.keysym);\n            else\n                m_mkKeys.insert(key.keysym);\n        } else {\n            if (keycodeToModifier(key.keycode))\n                m_mkMods.erase(key.keysym);\n            else\n                m_mkKeys.erase(key.keysym);\n        }\n    }\n\n    for (auto& k : m_keybinds) {\n        const bool SPECIALDISPATCHER = k->handler == \"global\" || k->handler == \"pass\" || k->handler == \"sendshortcut\" || k->handler == \"mouse\";\n        const bool SPECIALTRIGGERED  = std::ranges::find_if(m_pressedSpecialBinds, [&](const auto& other) { return other == k; }) != m_pressedSpecialBinds.end();\n        const bool IGNORECONDITIONS =\n            SPECIALDISPATCHER && !pressed && SPECIALTRIGGERED; // ignore mods. Pass, global dispatchers should be released immediately once the key is released.\n\n        if (!k->dontInhibit && !*PDISABLEINHIBIT && PROTO::shortcutsInhibit->isInhibited())\n            continue;\n\n        if (!k->locked && g_pSessionLockManager->isSessionLocked())\n            continue;\n\n        if (!IGNORECONDITIONS && ((modmask != k->modmask && !k->ignoreMods) || (k->submap != m_currentSelectedSubmap && !k->submapUniversal) || k->shadowed))\n            continue;\n\n        if (device) {\n            if (k->deviceInclusive ^ k->devices.contains(device->m_hlName))\n                continue;\n        }\n\n        if (k->multiKey) {\n            switch (mkBindMatches(k)) {\n                case MK_NO_MATCH: continue;\n                case MK_PARTIAL_MATCH: found = true; continue;\n                case MK_FULL_MATCH: found = true;\n            }\n        } else if (!key.keyName.empty()) {\n            if (key.keyName != k->key)\n                continue;\n        } else if (k->keycode != 0) {\n            if (key.keycode != k->keycode)\n                continue;\n        } else if (k->catchAll) {\n            if (found || key.submapAtPress != m_currentSelectedSubmap)\n                continue;\n        } else {\n            // in this case, we only have the keysym to go off of for this keybind, and it's invalid\n            // since there might be something like keycode to match with other keybinds, try the next\n            if (key.keysym == XKB_KEY_NoSymbol)\n                continue;\n\n            // oMg such performance hit!!11!\n            // this little maneouver is gonna cost us 4µs\n            const auto KBKEY      = xkb_keysym_from_name(k->key.c_str(), XKB_KEYSYM_NO_FLAGS);\n            const auto KBKEYLOWER = xkb_keysym_from_name(k->key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);\n\n            if (KBKEY == XKB_KEY_NoSymbol && KBKEYLOWER == XKB_KEY_NoSymbol) {\n                // Keysym failed to resolve from the key name of the currently iterated bind.\n                // This happens for names such as `switch:off:Lid Switch` as well as some keys\n                // (such as yen and ro).\n                //\n                // We can't let compare a 0-value with currently pressed key below,\n                // because if this key also have no keysym (i.e. key.keysym == 0) it will incorrectly trigger the\n                // currently iterated bind. That's confirmed to be happening with yen and ro keys.\n                continue;\n            }\n\n            if (key.keysym != KBKEY && key.keysym != KBKEYLOWER)\n                continue;\n        }\n\n        if (pressed && k->release && !SPECIALDISPATCHER) {\n            if (k->nonConsuming)\n                continue;\n\n            found = true; // suppress the event\n            continue;\n        }\n\n        if (!pressed) {\n            // Require mods to be matching when the key was first pressed.\n            if (key.modmaskAtPressTime != modmask && !k->ignoreMods) {\n                // Handle properly `bindr` where a key is itself a bind mod for example:\n                // \"bindr = SUPER, SUPER_L, exec, $launcher\".\n                // This needs to be handled separately for the above case, because `key.modmaskAtPressTime` is set\n                // from currently pressed keys as programs see them, but it doesn't yet include the currently\n                // pressed mod key, which is still being handled internally.\n                if (keycodeToModifier(key.keycode) == key.modmaskAtPressTime)\n                    continue;\n\n            } else if (!k->release && !SPECIALDISPATCHER) {\n                if (k->nonConsuming)\n                    continue;\n\n                found = true; // suppress the event\n                continue;\n            }\n\n            // Require mouse to stay inside drag_threshold for clicks, outside for drags\n            // Check if either a mouse bind has triggered or currently over the threshold (maybe there is no mouse bind on the same key)\n            const auto THRESHOLDREACHED = key.mousePosAtPress.distanceSq(g_pInputManager->getMouseCoordsInternal()) > std::pow(*PDRAGTHRESHOLD, 2);\n            if (k->click && (g_layoutManager->dragController()->dragThresholdReached() || THRESHOLDREACHED))\n                continue;\n            else if (k->drag && !g_layoutManager->dragController()->dragThresholdReached() && !THRESHOLDREACHED)\n                continue;\n        }\n\n        if (pressed && k->longPress) {\n            const auto PACTIVEKEEB = g_pSeatManager->m_keyboard.lock();\n\n            m_longPressTimer->updateTimeout(std::chrono::milliseconds(PACTIVEKEEB->m_repeatDelay));\n            m_lastLongPressKeybind = k;\n\n            continue;\n        }\n\n        const auto DISPATCHER = m_dispatchers.find(k->mouse ? \"mouse\" : k->handler);\n\n        if (SPECIALTRIGGERED && !pressed)\n            std::erase_if(m_pressedSpecialBinds, [&](const auto& other) { return other == k; });\n        else if (SPECIALDISPATCHER && pressed)\n            m_pressedSpecialBinds.emplace_back(k);\n\n        // Should never happen, as we check in the ConfigManager, but oh well\n        if (DISPATCHER == m_dispatchers.end()) {\n            Log::logger->log(Log::ERR, \"Invalid handler in a keybind! (handler {} does not exist)\", k->handler);\n        } else {\n            // call the dispatcher\n            Log::logger->log(Log::DEBUG, \"Keybind triggered, calling dispatcher ({}, {}, {}, {})\", modmask, key.keyName, key.keysym, DISPATCHER->first);\n\n            m_passPressed = sc<int>(pressed);\n\n            // if the dispatchers says to pass event then we will\n            if (k->handler == \"mouse\")\n                res = DISPATCHER->second((pressed ? \"1\" : \"0\") + k->arg);\n            else\n                res = DISPATCHER->second(k->arg);\n\n            m_passPressed = -1;\n\n            if (k->handler == \"submap\") {\n                found = true; // don't process keybinds on submap change.\n                break;\n            }\n            if (k->handler != \"submap\" && !k->submap.reset.empty())\n                setSubmap(k->submap.reset);\n        }\n\n        if (pressed && k->repeat) {\n            const auto KEEB = keyboard ? keyboard : g_pSeatManager->m_keyboard.lock();\n            m_repeatKeyRate = KEEB->m_repeatRate;\n\n            m_activeKeybinds.emplace_back(k);\n            m_repeatKeyTimer->updateTimeout(std::chrono::milliseconds(KEEB->m_repeatDelay));\n        }\n\n        if (!k->nonConsuming)\n            found = true;\n    }\n\n    g_layoutManager->dragController()->resetDragThresholdReached();\n\n    // if keybind wasn't found (or dispatcher said to) then pass event\n    res.passEvent |= !found;\n\n    if (!found && !*PDISABLEINHIBIT && PROTO::shortcutsInhibit->isInhibited()) {\n        Log::logger->log(Log::DEBUG, \"Keybind handling is disabled due to an inhibitor\");\n\n        res.success = false;\n        if (res.error.empty())\n            res.error = \"Keybind handling is disabled due to an inhibitor\";\n    }\n\n    return res;\n}\n\nvoid CKeybindManager::shadowKeybinds(const xkb_keysym_t& doesntHave, const uint32_t doesntHaveCode) {\n    // shadow disables keybinds after one has been triggered\n\n    for (auto& k : m_keybinds) {\n\n        bool shadow = false;\n\n        if (k->handler == \"global\" || k->transparent)\n            continue; // can't be shadowed\n\n        if (k->multiKey && (mkBindMatches(k) == MK_FULL_MATCH))\n            shadow = true;\n        else {\n            const auto KBKEY      = xkb_keysym_from_name(k->key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);\n            const auto KBKEYUPPER = xkb_keysym_to_upper(KBKEY);\n\n            for (auto const& pk : m_pressedKeys) {\n                if ((pk.keysym != 0 && (pk.keysym == KBKEY || pk.keysym == KBKEYUPPER))) {\n                    shadow = true;\n\n                    if (pk.keysym == doesntHave && doesntHave != 0) {\n                        shadow = false;\n                        break;\n                    }\n                }\n\n                if (pk.keycode != 0 && pk.keycode == k->keycode) {\n                    shadow = true;\n\n                    if (pk.keycode == doesntHaveCode && doesntHaveCode != 0) {\n                        shadow = false;\n                        break;\n                    }\n                }\n            }\n        }\n\n        k->shadowed = shadow;\n    }\n}\n\nbool CKeybindManager::handleVT(xkb_keysym_t keysym) {\n    // Handles the CTRL+ALT+FX TTY keybinds\n    if (keysym < XKB_KEY_XF86Switch_VT_1 || keysym > XKB_KEY_XF86Switch_VT_12)\n        return false;\n\n    // beyond this point, return true to not handle anything else.\n    // we'll avoid printing shit to active windows.\n\n    if (g_pCompositor->m_aqBackend->hasSession()) {\n        const unsigned int TTY = keysym - XKB_KEY_XF86Switch_VT_1 + 1;\n\n        const auto         CURRENT_TTY = g_pCompositor->getVTNr();\n\n        if (!CURRENT_TTY.has_value() || *CURRENT_TTY == TTY)\n            return true;\n\n        Log::logger->log(Log::DEBUG, \"Switching from VT {} to VT {}\", *CURRENT_TTY, TTY);\n\n        g_pCompositor->m_aqBackend->session->switchVT(TTY);\n    }\n\n    return true;\n}\n\nbool CKeybindManager::handleInternalKeybinds(xkb_keysym_t keysym) {\n    if (handleVT(keysym))\n        return true;\n\n    // handle ESC while in kill mode\n    if (g_pInputManager->getClickMode() == CLICKMODE_KILL) {\n        const auto KBKEY = xkb_keysym_from_name(\"ESCAPE\", XKB_KEYSYM_CASE_INSENSITIVE);\n\n        if (keysym == KBKEY) {\n            g_pInputManager->setClickMode(CLICKMODE_DEFAULT);\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// Dispatchers\nSDispatchResult CKeybindManager::spawn(std::string args) {\n    const auto PROC = spawnWithRules(args, nullptr);\n    if (!PROC.has_value())\n        return {.success = false, .error = std::format(\"Failed to start process. No closing bracket in exec rule. {}\", args)};\n    return {.success = PROC.value() > 0, .error = std::format(\"Failed to start process {}\", args)};\n}\n\nstd::optional<uint64_t> CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) {\n\n    args = trim(args);\n\n    std::string RULES = \"\";\n\n    if (args[0] == '[') {\n        // we have exec rules\n        const auto end = args.find_first_of(']');\n        if (end == std::string::npos)\n            return std::nullopt;\n\n        RULES = args.substr(1, end - 1);\n        args  = args.substr(end + 1);\n    }\n\n    std::string execToken = \"\";\n\n    if (!RULES.empty()) {\n        auto           rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES));\n\n        const auto     TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1));\n\n        const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, TOKEN);\n        rule->markAsExecRule(TOKEN, PROC, false /* TODO: could be nice. */);\n        rule->registerMatch(Desktop::Rule::RULE_PROP_EXEC_TOKEN, TOKEN);\n        rule->registerMatch(Desktop::Rule::RULE_PROP_EXEC_PID, std::to_string(PROC));\n        Desktop::Rule::ruleEngine()->registerRule(std::move(rule));\n        Log::logger->log(Log::DEBUG, \"Applied rule arguments for exec.\");\n        return PROC;\n    }\n    const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, execToken);\n\n    return PROC;\n}\n\nSDispatchResult CKeybindManager::spawnRaw(std::string args) {\n    const uint64_t PROC = spawnRawProc(args, nullptr);\n    return {.success = PROC > 0, .error = std::format(\"Failed to start process {}\", args)};\n}\n\nuint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken) {\n    Log::logger->log(Log::DEBUG, \"Executing {}\", args);\n\n    const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace);\n\n    pid_t      child = fork();\n    if (child < 0) {\n        Log::logger->log(Log::DEBUG, \"Fail to fork\");\n        return 0;\n    }\n    if (child == 0) {\n        // run in child\n        g_pCompositor->restoreNofile();\n\n        sigset_t set;\n        sigemptyset(&set);\n        sigprocmask(SIG_SETMASK, &set, nullptr);\n\n        for (auto const& e : HLENV) {\n            setenv(e.first.c_str(), e.second.c_str(), 1);\n        }\n        setenv(\"WAYLAND_DISPLAY\", g_pCompositor->m_wlDisplaySocket.c_str(), 1);\n        if (!execRuleToken.empty())\n            setenv(Desktop::Rule::EXEC_RULE_ENV_NAME, execRuleToken.c_str(), true);\n\n        int devnull = open(\"/dev/null\", O_WRONLY | O_CLOEXEC);\n        if (devnull != -1) {\n            dup2(devnull, STDOUT_FILENO);\n            dup2(devnull, STDERR_FILENO);\n            close(devnull);\n        }\n\n        execl(\"/bin/sh\", \"/bin/sh\", \"-c\", args.c_str(), nullptr);\n\n        // exit child\n        _exit(0);\n    }\n    // run in parent\n\n    Log::logger->log(Log::DEBUG, \"Process Created with pid {}\", child);\n\n    return child;\n}\n\nSDispatchResult CKeybindManager::killActive(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"killActive: no window found\");\n        return {.success = false, .error = \"killActive: no window found\"};\n    }\n\n    kill(PWINDOW->getPID(), SIGKILL);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::closeActive(std::string args) {\n    if (Desktop::focusState()->window() && Desktop::focusState()->window()->m_closeableSince > Time::steadyNow())\n        return {.success = false, .error = \"can't close window, it's not closeable yet (noclosefor)\"};\n\n    g_pCompositor->closeWindow(Desktop::focusState()->window());\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::closeWindow(std::string args) {\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(args);\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"closeWindow: no window found\");\n        return {.success = false, .error = \"closeWindow: no window found\"};\n    }\n\n    if (PWINDOW->m_closeableSince > Time::steadyNow())\n        return {.success = false, .error = \"can't close window, it's not closeable yet (noclosefor)\"};\n\n    g_pCompositor->closeWindow(PWINDOW);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::killWindow(std::string args) {\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(args);\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"killWindow: no window found\");\n        return {.success = false, .error = \"killWindow: no window found\"};\n    }\n\n    kill(PWINDOW->getPID(), SIGKILL);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::signalActive(std::string args) {\n    if (!isNumber(args))\n        return {.success = false, .error = \"signalActive: signal has to be int\"};\n\n    try {\n        const auto SIGNALNUM = std::stoi(args);\n        if (SIGNALNUM < 1 || SIGNALNUM > 31) {\n            Log::logger->log(Log::ERR, \"signalActive: invalid signal number {}\", SIGNALNUM);\n            return {.success = false, .error = std::format(\"signalActive: invalid signal number {}\", SIGNALNUM)};\n        }\n        kill(Desktop::focusState()->window()->getPID(), SIGNALNUM);\n    } catch (const std::exception& e) {\n        Log::logger->log(Log::ERR, \"signalActive: invalid signal format \\\"{}\\\"\", args);\n        return {.success = false, .error = std::format(\"signalActive: invalid signal format \\\"{}\\\"\", args)};\n    }\n\n    kill(Desktop::focusState()->window()->getPID(), std::stoi(args));\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::signalWindow(std::string args) {\n    const auto WINDOWREGEX = args.substr(0, args.find_first_of(','));\n    const auto SIGNAL      = args.substr(args.find_first_of(',') + 1);\n\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"signalWindow: no window\");\n        return {.success = false, .error = \"signalWindow: no window\"};\n    }\n\n    if (!std::ranges::all_of(SIGNAL, ::isdigit))\n        return {.success = false, .error = \"signalWindow: signal has to be int\"};\n\n    try {\n        const auto SIGNALNUM = std::stoi(SIGNAL);\n        if (SIGNALNUM < 1 || SIGNALNUM > 31) {\n            Log::logger->log(Log::ERR, \"signalWindow: invalid signal number {}\", SIGNALNUM);\n            return {.success = false, .error = std::format(\"signalWindow: invalid signal number {}\", SIGNALNUM)};\n        }\n        kill(PWINDOW->getPID(), SIGNALNUM);\n    } catch (const std::exception& e) {\n        Log::logger->log(Log::ERR, \"signalWindow: invalid signal format \\\"{}\\\"\", SIGNAL);\n        return {.success = false, .error = std::format(\"signalWindow: invalid signal format \\\"{}\\\"\", SIGNAL)};\n    }\n\n    return {};\n}\n\nvoid CKeybindManager::clearKeybinds() {\n    m_keybinds.clear();\n}\n\nstatic SDispatchResult toggleActiveFloatingCore(std::string args, std::optional<bool> floatState) {\n    PHLWINDOW PWINDOW = nullptr;\n\n    if (args != \"active\" && args.length() > 1)\n        PWINDOW = g_pCompositor->getWindowByRegex(args);\n    else\n        PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    if (floatState.has_value() && floatState == PWINDOW->m_isFloating)\n        return {};\n\n    // remove drag status\n    if (g_layoutManager->dragController()->target())\n        CKeybindManager::changeMouseBindMode(MBIND_INVALID);\n\n    g_layoutManager->changeFloatingMode(PWINDOW->layoutTarget());\n\n    if (PWINDOW->m_workspace) {\n        PWINDOW->m_workspace->updateWindows();\n        PWINDOW->m_workspace->updateWindowData();\n    }\n\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::toggleActiveFloating(std::string args) {\n    return toggleActiveFloatingCore(args, std::nullopt);\n}\n\nSDispatchResult CKeybindManager::setActiveFloating(std::string args) {\n    return toggleActiveFloatingCore(args, true);\n}\n\nSDispatchResult CKeybindManager::setActiveTiled(std::string args) {\n    return toggleActiveFloatingCore(args, false);\n}\n\nSDispatchResult CKeybindManager::centerWindow(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW || !PWINDOW->m_isFloating || PWINDOW->isFullscreen())\n        return {.success = false, .error = \"No floating window found\"};\n\n    const auto PMONITOR = PWINDOW->m_monitor.lock();\n\n    PWINDOW->layoutTarget()->setPositionGlobal(CBox{PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.F, PWINDOW->layoutTarget()->position().size()});\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::toggleActivePseudo(std::string args) {\n    PHLWINDOW PWINDOW = nullptr;\n\n    if (args != \"active\" && args.length() > 1)\n        PWINDOW = g_pCompositor->getWindowByRegex(args);\n    else\n        PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    PWINDOW->layoutTarget()->setPseudo(!PWINDOW->layoutTarget()->isPseudo());\n\n    return {};\n}\n\nstatic SWorkspaceIDName getWorkspaceToChangeFromArgs(std::string args, PHLWORKSPACE PCURRENTWORKSPACE, PHLMONITORREF PMONITOR) {\n    if (!args.starts_with(\"previous\")) {\n        return getWorkspaceIDNameFromString(args);\n    }\n\n    const bool             PER_MON = args.contains(\"_per_monitor\");\n    const SWorkspaceIDName PPREVWS = PER_MON ? Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE, PMONITOR.lock()) :\n                                               Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE);\n    // Do nothing if there's no previous workspace, otherwise switch to it.\n    if (PPREVWS.id == -1 || PPREVWS.id == PCURRENTWORKSPACE->m_id) {\n        Log::logger->log(Log::DEBUG, \"No previous workspace to change to\");\n        return {.id = WORKSPACE_NOT_CHANGED};\n    }\n\n    if (const auto PWORKSPACETOCHANGETO = g_pCompositor->getWorkspaceByID(PPREVWS.id); PWORKSPACETOCHANGETO) {\n        return {.id = PWORKSPACETOCHANGETO->m_id, .name = PWORKSPACETOCHANGETO->m_name};\n    }\n\n    return {.id = PPREVWS.id, .name = PPREVWS.name.empty() ? std::to_string(PPREVWS.id) : PPREVWS.name};\n}\n\nSDispatchResult CKeybindManager::changeworkspace(std::string args) {\n    // Workspace_back_and_forth being enabled means that an attempt to switch to\n    // the current workspace will instead switch to the previous.\n    static auto PBACKANDFORTH                 = CConfigValue<Hyprlang::INT>(\"binds:workspace_back_and_forth\");\n    static auto PWORKSPACECENTERON            = CConfigValue<Hyprlang::INT>(\"binds:workspace_center_on\");\n    static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue<Hyprlang::INT>(\"binds:hide_special_on_workspace_change\");\n\n    const auto  PMONITOR = Desktop::focusState()->monitor();\n\n    if (!PMONITOR)\n        return {.success = false, .error = \"Last monitor not found\"};\n\n    const auto PCURRENTWORKSPACE = PMONITOR->m_activeWorkspace;\n    const bool EXPLICITPREVIOUS  = args.contains(\"previous\");\n\n    const auto& [workspaceToChangeTo, workspaceName, isAutoID] = getWorkspaceToChangeFromArgs(args, PCURRENTWORKSPACE, PMONITOR);\n    if (workspaceToChangeTo == WORKSPACE_INVALID) {\n        Log::logger->log(Log::ERR, \"Error in changeworkspace, invalid value\");\n        return {.success = false, .error = \"Error in changeworkspace, invalid value\"};\n    }\n\n    if (workspaceToChangeTo == WORKSPACE_NOT_CHANGED)\n        return {};\n\n    const SWorkspaceIDName PPREVWS = args.contains(\"_per_monitor\") ? Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE, PMONITOR) :\n                                                                     Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE);\n\n    const bool             BISWORKSPACECURRENT = workspaceToChangeTo == PCURRENTWORKSPACE->m_id;\n    if (BISWORKSPACECURRENT && (!(*PBACKANDFORTH || EXPLICITPREVIOUS) || PPREVWS.id == -1)) {\n        if (*PHIDESPECIALONWORKSPACECHANGE)\n            PMONITOR->setSpecialWorkspace(nullptr);\n\n        return {.success = false, .error = \"Previous workspace doesn't exist\"};\n    }\n\n    g_pInputManager->unconstrainMouse();\n    g_pInputManager->m_emptyFocusCursorSet = false;\n\n    auto pWorkspaceToChangeTo = g_pCompositor->getWorkspaceByID(BISWORKSPACECURRENT ? PPREVWS.id : workspaceToChangeTo);\n    if (!pWorkspaceToChangeTo)\n        pWorkspaceToChangeTo =\n            g_pCompositor->createNewWorkspace(BISWORKSPACECURRENT ? PPREVWS.id : workspaceToChangeTo, PMONITOR->m_id, BISWORKSPACECURRENT ? PPREVWS.name : workspaceName);\n\n    if (!BISWORKSPACECURRENT && pWorkspaceToChangeTo->m_isSpecialWorkspace) {\n        PMONITOR->setSpecialWorkspace(pWorkspaceToChangeTo);\n        g_pInputManager->simulateMouseMovement();\n        return {};\n    }\n\n    g_pInputManager->releaseAllMouseButtons();\n\n    const auto PMONITORWORKSPACEOWNER = PMONITOR == pWorkspaceToChangeTo->m_monitor ? PMONITOR : pWorkspaceToChangeTo->m_monitor.lock();\n\n    if (!PMONITORWORKSPACEOWNER)\n        return {.success = false, .error = \"Workspace to switch to has no monitor\"};\n\n    updateRelativeCursorCoords();\n\n    Desktop::focusState()->rawMonitorFocus(PMONITORWORKSPACEOWNER);\n\n    if (*PHIDESPECIALONWORKSPACECHANGE)\n        PMONITORWORKSPACEOWNER->setSpecialWorkspace(nullptr);\n    PMONITORWORKSPACEOWNER->changeWorkspace(pWorkspaceToChangeTo, false, true);\n\n    if (PMONITOR != PMONITORWORKSPACEOWNER) {\n        Vector2D middle = PMONITORWORKSPACEOWNER->middle();\n        if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) {\n            Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);\n            if (*PWORKSPACECENTERON == 1)\n                middle = PLAST->middle();\n        }\n        g_pCompositor->warpCursorTo(middle);\n    }\n\n    if (!g_pInputManager->m_lastFocusOnLS) {\n        if (Desktop::focusState()->surface())\n            g_pInputManager->sendMotionEventsToFocused();\n        else\n            g_pInputManager->simulateMouseMovement();\n    }\n\n    const static auto PWARPONWORKSPACECHANGE = CConfigValue<Hyprlang::INT>(\"cursor:warp_on_change_workspace\");\n\n    if (*PWARPONWORKSPACECHANGE > 0) {\n        auto PLAST     = pWorkspaceToChangeTo->getLastFocusedWindow();\n        auto HLSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock());\n\n        if (PLAST && (!HLSurface || HLSurface->view()->type() == Desktop::View::VIEW_TYPE_WINDOW))\n            PLAST->warpCursor(*PWARPONWORKSPACECHANGE == 2);\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::fullscreenActive(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n    const auto ARGS    = CConstVarList(args, 2, ' ');\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    const eFullscreenMode MODE = ARGS.size() > 0 && ARGS[0] == \"1\" ? FSMODE_MAXIMIZED : FSMODE_FULLSCREEN;\n\n    if (ARGS.size() <= 1 || ARGS[1] == \"toggle\") {\n        if (PWINDOW->isEffectiveInternalFSMode(MODE))\n            g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n        else\n            g_pCompositor->setWindowFullscreenInternal(PWINDOW, MODE);\n    } else {\n        if (ARGS[1] == \"set\")\n            g_pCompositor->setWindowFullscreenInternal(PWINDOW, MODE);\n        else if (ARGS[1] == \"unset\")\n            g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::fullscreenStateActive(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n    const auto ARGS    = CVarList(args, 3, ' ');\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    PWINDOW->m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_SET_PROP));\n\n    int internalMode, clientMode;\n    try {\n        internalMode = std::stoi(ARGS[0]);\n    } catch (std::exception& e) { internalMode = -1; }\n    try {\n        clientMode = std::stoi(ARGS[1]);\n    } catch (std::exception& e) { clientMode = -1; }\n\n    const Desktop::View::SFullscreenState STATE =\n        Desktop::View::SFullscreenState{.internal = (internalMode != -1 ? sc<eFullscreenMode>(internalMode) : PWINDOW->m_fullscreenState.internal),\n                                        .client   = (clientMode != -1 ? sc<eFullscreenMode>(clientMode) : PWINDOW->m_fullscreenState.client)};\n\n    if (ARGS.size() <= 2 || ARGS[2] == \"toggle\") {\n        if (internalMode != -1 && clientMode != -1 && PWINDOW->m_fullscreenState.internal == STATE.internal && PWINDOW->m_fullscreenState.client == STATE.client)\n            g_pCompositor->setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = FSMODE_NONE, .client = FSMODE_NONE});\n        else if (internalMode != -1 && clientMode == -1 && PWINDOW->m_fullscreenState.internal == STATE.internal)\n            g_pCompositor->setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = FSMODE_NONE, .client = PWINDOW->m_fullscreenState.client});\n        else if (internalMode == -1 && clientMode != -1 && PWINDOW->m_fullscreenState.client == STATE.client)\n            g_pCompositor->setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = FSMODE_NONE});\n        else\n            g_pCompositor->setWindowFullscreenState(PWINDOW, STATE);\n    } else if (ARGS[2] == \"set\") {\n        g_pCompositor->setWindowFullscreenState(PWINDOW, STATE);\n    }\n\n    PWINDOW->m_ruleApplicator->syncFullscreenOverride(\n        Desktop::Types::COverridableVar(PWINDOW->m_fullscreenState.internal == PWINDOW->m_fullscreenState.client, Desktop::Types::PRIORITY_SET_PROP));\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) {\n\n    PHLWINDOW PWINDOW = nullptr;\n\n    if (args.contains(',')) {\n        PWINDOW = g_pCompositor->getWindowByRegex(args.substr(args.find_last_of(',') + 1));\n        args    = args.substr(0, args.find_last_of(','));\n    } else {\n        PWINDOW = Desktop::focusState()->window();\n    }\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    const auto& [WORKSPACEID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(args);\n    if (WORKSPACEID == WORKSPACE_INVALID) {\n        Log::logger->log(Log::DEBUG, \"Invalid workspace in moveActiveToWorkspace\");\n        return {.success = false, .error = \"Invalid workspace in moveActiveToWorkspace\"};\n    }\n\n    if (WORKSPACEID == PWINDOW->workspaceID()) {\n        Log::logger->log(Log::DEBUG, \"Not moving to workspace because it didn't change.\");\n        return {.success = false, .error = \"Not moving to workspace because it didn't change.\"};\n    }\n\n    auto       pWorkspace = g_pCompositor->getWorkspaceByID(WORKSPACEID);\n    PHLMONITOR pMonitor   = nullptr;\n    const auto POLDWS     = PWINDOW->m_workspace;\n\n    updateRelativeCursorCoords();\n\n    g_pHyprRenderer->damageWindow(PWINDOW);\n\n    if (pWorkspace) {\n        const auto FULLSCREENMODE = PWINDOW->m_fullscreenState.internal;\n        g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace);\n        pMonitor = pWorkspace->m_monitor.lock();\n        Desktop::focusState()->rawMonitorFocus(pMonitor);\n        g_pCompositor->setWindowFullscreenInternal(PWINDOW, FULLSCREENMODE);\n    } else {\n        pWorkspace = g_pCompositor->createNewWorkspace(WORKSPACEID, PWINDOW->monitorID(), workspaceName, false);\n        pMonitor   = pWorkspace->m_monitor.lock();\n        g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace);\n    }\n\n    POLDWS->m_lastFocusedWindow = POLDWS->getFirstWindow();\n\n    if (pWorkspace->m_isSpecialWorkspace)\n        pMonitor->setSpecialWorkspace(pWorkspace);\n    else if (POLDWS->m_isSpecialWorkspace)\n        POLDWS->m_monitor.lock()->setSpecialWorkspace(nullptr);\n\n    pMonitor->changeWorkspace(pWorkspace);\n\n    Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND);\n    PWINDOW->warpCursor();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) {\n    PHLWINDOW PWINDOW = nullptr;\n\n    if (args.contains(',')) {\n        PWINDOW = g_pCompositor->getWindowByRegex(args.substr(args.find_last_of(',') + 1));\n        args    = args.substr(0, args.find_last_of(','));\n    } else {\n        PWINDOW = Desktop::focusState()->window();\n    }\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    const auto& [WORKSPACEID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(args);\n    if (WORKSPACEID == WORKSPACE_INVALID) {\n        Log::logger->log(Log::ERR, \"Error in moveActiveToWorkspaceSilent, invalid value\");\n        return {.success = false, .error = \"Error in moveActiveToWorkspaceSilent, invalid value\"};\n    }\n\n    if (WORKSPACEID == PWINDOW->workspaceID())\n        return {};\n\n    g_pHyprRenderer->damageWindow(PWINDOW);\n\n    auto       pWorkspace = g_pCompositor->getWorkspaceByID(WORKSPACEID);\n    const auto OLDMIDDLE  = PWINDOW->middle();\n\n    if (pWorkspace) {\n        g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace);\n    } else {\n        pWorkspace = g_pCompositor->createNewWorkspace(WORKSPACEID, PWINDOW->monitorID(), workspaceName, false);\n        g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace);\n    }\n\n    if (PWINDOW == Desktop::focusState()->window()) {\n        if (const auto PATCOORDS =\n                g_pCompositor->vectorToWindowUnified(OLDMIDDLE, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, PWINDOW);\n            PATCOORDS)\n            Desktop::focusState()->fullWindowFocus(PATCOORDS, Desktop::FOCUS_REASON_KEYBIND);\n        else\n            g_pInputManager->refocus();\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveFocusTo(std::string args) {\n    static auto      PFULLCYCLE       = CConfigValue<Hyprlang::INT>(\"binds:movefocus_cycles_fullscreen\");\n    static auto      PGROUPCYCLE      = CConfigValue<Hyprlang::INT>(\"binds:movefocus_cycles_groupfirst\");\n    static auto      PMONITORFALLBACK = CConfigValue<Hyprlang::INT>(\"binds:window_direction_monitor_fallback\");\n    Math::eDirection dir              = Math::fromChar(args[0]);\n\n    if (dir == Math::DIRECTION_DEFAULT) {\n        Log::logger->log(Log::ERR, \"Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0]);\n        return {.success = false, .error = std::format(\"Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0])};\n    }\n\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n    if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) {\n        if (*PMONITORFALLBACK)\n            tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir));\n        return {};\n    }\n\n    const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ?\n        g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) :\n        g_pCompositor->getWindowInDirection(PLASTWINDOW, dir);\n\n    // Prioritize focus change within groups if the window is a part of it.\n    if (*PGROUPCYCLE && PLASTWINDOW->m_group) {\n        auto isTheOnlyGroupOnWs = !PWINDOWTOCHANGETO && g_pCompositor->m_monitors.size() == 1;\n        if (dir == Math::DIRECTION_LEFT && (PLASTWINDOW != PLASTWINDOW->m_group->head() || isTheOnlyGroupOnWs)) {\n            PLASTWINDOW->m_group->moveCurrent(false);\n            return {};\n        }\n\n        else if (dir == Math::DIRECTION_RIGHT && (PLASTWINDOW != PLASTWINDOW->m_group->tail() || isTheOnlyGroupOnWs)) {\n            PLASTWINDOW->m_group->moveCurrent(true);\n            return {};\n        }\n    }\n\n    // Found window in direction, switch to it\n    if (PWINDOWTOCHANGETO) {\n        switchToWindow(PWINDOWTOCHANGETO, *PFULLCYCLE && PLASTWINDOW->isFullscreen());\n        return {};\n    }\n\n    Log::logger->log(Log::DEBUG, \"No window found in direction {}, looking for a monitor\", Math::toString(dir));\n\n    if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)))\n        return {};\n\n    static auto PNOFALLBACK = CConfigValue<Hyprlang::INT>(\"general:no_focus_fallback\");\n    if (*PNOFALLBACK)\n        return {.success = false, .error = std::format(\"Nothing to focus to in direction {}\", Math::toString(dir))};\n\n    Log::logger->log(Log::DEBUG, \"No monitor found in direction {}, getting the inverse edge\", Math::toString(dir));\n\n    const auto PMONITOR = PLASTWINDOW->m_monitor.lock();\n\n    if (!PMONITOR)\n        return {.success = false, .error = \"last window has no monitor?\"};\n\n    if (dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_RIGHT) {\n        if (STICKS(PLASTWINDOW->m_position.x, PMONITOR->m_position.x) && STICKS(PLASTWINDOW->m_size.x, PMONITOR->m_size.x))\n            return {.success = false, .error = \"move does not make sense, would return back\"};\n    } else if (STICKS(PLASTWINDOW->m_position.y, PMONITOR->m_position.y) && STICKS(PLASTWINDOW->m_size.y, PMONITOR->m_size.y))\n        return {.success = false, .error = \"move does not make sense, would return back\"};\n\n    CBox box = PMONITOR->logicalBox();\n    switch (dir) {\n        case Math::DIRECTION_LEFT:\n            box.x += box.w;\n            box.w = 1;\n            break;\n        case Math::DIRECTION_RIGHT:\n            box.x -= 1;\n            box.w = 1;\n            break;\n        case Math::DIRECTION_UP:\n            box.y += box.h;\n            box.h = 1;\n            break;\n        case Math::DIRECTION_DOWN:\n            box.y -= 1;\n            box.h = 1;\n            break;\n        default: break;\n    }\n\n    const auto PWINDOWCANDIDATE = g_pCompositor->getWindowInDirection(box, PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace,\n                                                                      dir, PLASTWINDOW, PLASTWINDOW->m_isFloating);\n    if (PWINDOWCANDIDATE)\n        switchToWindow(PWINDOWCANDIDATE);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::focusUrgentOrLast(std::string args) {\n    const auto& HISTORY       = Desktop::History::windowTracker()->fullHistory();\n    const auto  PWINDOWURGENT = g_pCompositor->getUrgentWindow();\n    const auto  PWINDOWPREV   = Desktop::focusState()->window() ? (HISTORY.size() < 2 ? nullptr : HISTORY[1].lock()) : (HISTORY.empty() ? nullptr : HISTORY[0].lock());\n\n    if (!PWINDOWURGENT && !PWINDOWPREV)\n        return {.success = false, .error = \"Window not found\"};\n\n    switchToWindow(PWINDOWURGENT ? PWINDOWURGENT : PWINDOWPREV);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) {\n    const auto& HISTORY = Desktop::History::windowTracker()->fullHistory();\n\n    if (HISTORY.size() <= 1)\n        return {.success = false, .error = \"History too short\"};\n\n    const auto PWINDOWPREV = HISTORY[HISTORY.size() - 2].lock();\n\n    if (!PWINDOWPREV)\n        return {.success = false, .error = \"Window not found\"};\n\n    switchToWindow(PWINDOWPREV);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::swapActive(std::string args) {\n    const auto PLASTWINDOW       = Desktop::focusState()->window();\n    PHLWINDOW  PWINDOWTOCHANGETO = nullptr;\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"Window to swap with not found\"};\n\n    if (PLASTWINDOW->isFullscreen())\n        return {.success = false, .error = \"Can't swap fullscreen window\"};\n\n    if (isDirection(args)) {\n        Math::eDirection dir = Math::fromChar(args[0]);\n        PWINDOWTOCHANGETO    = g_pCompositor->getWindowInDirection(PLASTWINDOW, dir);\n    } else\n        PWINDOWTOCHANGETO = g_pCompositor->getWindowByRegex(args);\n\n    if (!PWINDOWTOCHANGETO || PWINDOWTOCHANGETO == PLASTWINDOW) {\n        Log::logger->log(Log::ERR, \"Can't swap with {}, invalid window\", args);\n        return {.success = false, .error = std::format(\"Can't swap with {}, invalid window\", args)};\n    }\n\n    Log::logger->log(Log::DEBUG, \"Swapping active window with {}\", args);\n\n    updateRelativeCursorCoords();\n    g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), PWINDOWTOCHANGETO->layoutTarget(), true);\n    PLASTWINDOW->warpCursor();\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveActiveTo(std::string args) {\n    bool silent = args.ends_with(\" silent\");\n    if (silent)\n        args = args.substr(0, args.length() - 7);\n\n    if (args.starts_with(\"mon:\")) {\n        const auto PNEWMONITOR = g_pCompositor->getMonitorFromString(args.substr(4));\n        if (!PNEWMONITOR)\n            return {.success = false, .error = std::format(\"Monitor {} not found\", args.substr(4))};\n\n        if (silent)\n            moveActiveToWorkspaceSilent(PNEWMONITOR->m_activeWorkspace->getConfigName());\n        else\n            moveActiveToWorkspace(PNEWMONITOR->m_activeWorkspace->getConfigName());\n\n        return {};\n    }\n\n    Math::eDirection dir = Math::fromChar(args[0]);\n    if (dir == Math::DIRECTION_DEFAULT) {\n        Log::logger->log(Log::ERR, \"Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0]);\n        return {.success = false, .error = std::format(\"Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0])};\n    }\n\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"Window to move not found\"};\n\n    if (PLASTWINDOW->isFullscreen())\n        return {.success = false, .error = \"Can't move fullscreen window\"};\n\n    updateRelativeCursorCoords();\n\n    g_layoutManager->moveInDirection(PLASTWINDOW->layoutTarget(), args, silent);\n    if (!silent)\n        PLASTWINDOW->warpCursor();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::toggleGroup(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    if (PWINDOW->isFullscreen())\n        g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE);\n\n    if (!PWINDOW->m_group)\n        PWINDOW->m_group = Desktop::View::CGroup::create({PWINDOW});\n    else\n        PWINDOW->m_group->destroy();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::changeGroupActive(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    if (!PWINDOW->m_group)\n        return {.success = false, .error = \"No group\"};\n\n    if (PWINDOW->m_group->size() == 1)\n        return {.success = false, .error = \"Only one window in group\"};\n\n    if (isNumber(args, false)) {\n        // index starts from '1'; '0' means last window\n        try {\n            const int INDEX = std::stoi(args);\n            if (INDEX <= 0)\n                PWINDOW->m_group->setCurrent(PWINDOW->m_group->size() - 1);\n            else\n                PWINDOW->m_group->setCurrent(INDEX - 1);\n        } catch (...) { return {.success = false, .error = \"invalid idx\"}; }\n\n        return {};\n    }\n\n    if (args != \"b\" && args != \"prev\")\n        PWINDOW->m_group->moveCurrent(true);\n    else\n        PWINDOW->m_group->moveCurrent(false);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::focusMonitor(std::string arg) {\n    const auto PMONITOR = g_pCompositor->getMonitorFromString(arg);\n    tryMoveFocusToMonitor(PMONITOR);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveCursorToCorner(std::string arg) {\n    if (!isNumber(arg)) {\n        Log::logger->log(Log::ERR, \"moveCursorToCorner, arg has to be a number.\");\n        return {.success = false, .error = \"moveCursorToCorner, arg has to be a number.\"};\n    }\n\n    const auto CORNER = std::stoi(arg);\n\n    if (CORNER < 0 || CORNER > 3) {\n        Log::logger->log(Log::ERR, \"moveCursorToCorner, corner not 0 - 3.\");\n        return {.success = false, .error = \"moveCursorToCorner, corner not 0 - 3.\"};\n    }\n\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    switch (CORNER) {\n        case 0:\n            // bottom left\n            g_pCompositor->warpCursorTo({PWINDOW->m_realPosition->value().x, PWINDOW->m_realPosition->value().y + PWINDOW->m_realSize->value().y}, true);\n            break;\n        case 1:\n            // bottom right\n            g_pCompositor->warpCursorTo({PWINDOW->m_realPosition->value().x + PWINDOW->m_realSize->value().x, PWINDOW->m_realPosition->value().y + PWINDOW->m_realSize->value().y},\n                                        true);\n            break;\n        case 2:\n            // top right\n            g_pCompositor->warpCursorTo({PWINDOW->m_realPosition->value().x + PWINDOW->m_realSize->value().x, PWINDOW->m_realPosition->value().y}, true);\n            break;\n        case 3:\n            // top left\n            g_pCompositor->warpCursorTo({PWINDOW->m_realPosition->value().x, PWINDOW->m_realPosition->value().y}, true);\n            break;\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveCursor(std::string args) {\n    std::string x_str, y_str;\n    int         x, y;\n\n    size_t      i = args.find_first_of(' ');\n    if (i == std::string::npos) {\n        Log::logger->log(Log::ERR, \"moveCursor, takes 2 arguments.\");\n        return {.success = false, .error = \"moveCursor, takes 2 arguments\"};\n    }\n\n    x_str = args.substr(0, i);\n    y_str = args.substr(i + 1);\n\n    if (!isNumber(x_str)) {\n        Log::logger->log(Log::ERR, \"moveCursor, x argument has to be a number.\");\n        return {.success = false, .error = \"moveCursor, x argument has to be a number.\"};\n    }\n    if (!isNumber(y_str)) {\n        Log::logger->log(Log::ERR, \"moveCursor, y argument has to be a number.\");\n        return {.success = false, .error = \"moveCursor, y argument has to be a number.\"};\n    }\n\n    x = std::stoi(x_str);\n    y = std::stoi(y_str);\n\n    g_pCompositor->warpCursorTo({x, y}, true);\n    g_pInputManager->simulateMouseMovement();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::workspaceOpt(std::string args) {\n    return {.success = false, .error = \"workspaceopt is deprecated\"};\n}\n\nSDispatchResult CKeybindManager::renameWorkspace(std::string args) {\n    try {\n        const auto FIRSTSPACEPOS = args.find_first_of(' ');\n        if (FIRSTSPACEPOS != std::string::npos) {\n            int         workspace = std::stoi(args.substr(0, FIRSTSPACEPOS));\n            std::string name      = args.substr(FIRSTSPACEPOS + 1);\n            if (const auto& PWS = g_pCompositor->getWorkspaceByID(workspace); PWS)\n                PWS->rename(name);\n            else\n                return {.success = false, .error = \"No such workspace\"};\n        } else if (const auto& PWS = g_pCompositor->getWorkspaceByID(std::stoi(args)); PWS)\n            PWS->rename(\"\");\n        else\n            return {.success = false, .error = \"No such workspace\"};\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, R\"(Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. \"{}\": \"{}\")\", args, e.what());\n        return {.success = false, .error = std::format(R\"(Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. \"{}\": \"{}\")\", args, e.what())};\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::exitHyprland(std::string argz) {\n    g_pConfigManager->dispatchExecShutdown();\n\n    if (g_pCompositor->m_finalRequests)\n        return {}; // Exiting deferred until requests complete\n\n    g_pCompositor->stopCompositor();\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveCurrentWorkspaceToMonitor(std::string args) {\n    PHLMONITOR PMONITOR = g_pCompositor->getMonitorFromString(args);\n\n    if (!PMONITOR) {\n        Log::logger->log(Log::ERR, \"Ignoring moveCurrentWorkspaceToMonitor: monitor doesn't exist\");\n        return {.success = false, .error = \"Ignoring moveCurrentWorkspaceToMonitor: monitor doesn't exist\"};\n    }\n\n    // get the current workspace\n    const auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;\n    if (!PCURRENTWORKSPACE) {\n        Log::logger->log(Log::ERR, \"moveCurrentWorkspaceToMonitor invalid workspace!\");\n        return {.success = false, .error = \"moveCurrentWorkspaceToMonitor invalid workspace!\"};\n    }\n\n    g_pCompositor->moveWorkspaceToMonitor(PCURRENTWORKSPACE, PMONITOR);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveWorkspaceToMonitor(std::string args) {\n    if (!args.contains(' '))\n        return {.success = false, .error = \"Invalid arguments, expected: workspace monitor\"};\n\n    std::string workspace = args.substr(0, args.find_first_of(' '));\n    std::string monitor   = args.substr(args.find_first_of(' ') + 1);\n\n    const auto  PMONITOR = g_pCompositor->getMonitorFromString(monitor);\n\n    if (!PMONITOR) {\n        Log::logger->log(Log::ERR, \"Ignoring moveWorkspaceToMonitor: monitor doesn't exist\");\n        return {.success = false, .error = \"Ignoring moveWorkspaceToMonitor: monitor doesn't exist\"};\n    }\n\n    const auto WORKSPACEID = getWorkspaceIDNameFromString(workspace).id;\n\n    if (WORKSPACEID == WORKSPACE_INVALID) {\n        Log::logger->log(Log::ERR, \"moveWorkspaceToMonitor invalid workspace!\");\n        return {.success = false, .error = \"moveWorkspaceToMonitor invalid workspace!\"};\n    }\n\n    const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(WORKSPACEID);\n\n    if (!PWORKSPACE) {\n        Log::logger->log(Log::ERR, \"moveWorkspaceToMonitor workspace doesn't exist!\");\n        return {.success = false, .error = \"moveWorkspaceToMonitor workspace doesn't exist!\"};\n    }\n\n    g_pCompositor->moveWorkspaceToMonitor(PWORKSPACE, PMONITOR);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) {\n    auto [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(args);\n    if (workspaceID == WORKSPACE_INVALID) {\n        Log::logger->log(Log::ERR, \"focusWorkspaceOnCurrentMonitor invalid workspace!\");\n        return {.success = false, .error = \"focusWorkspaceOnCurrentMonitor invalid workspace!\"};\n    }\n\n    const auto PCURRMONITOR = Desktop::focusState()->monitor();\n\n    if (!PCURRMONITOR) {\n        Log::logger->log(Log::ERR, \"focusWorkspaceOnCurrentMonitor monitor doesn't exist!\");\n        return {.success = false, .error = \"focusWorkspaceOnCurrentMonitor monitor doesn't exist!\"};\n    }\n\n    auto pWorkspace = g_pCompositor->getWorkspaceByID(workspaceID);\n\n    if (!pWorkspace) {\n        pWorkspace = g_pCompositor->createNewWorkspace(workspaceID, PCURRMONITOR->m_id, workspaceName);\n        // we can skip the moving, since it's already on the current monitor\n        changeworkspace(pWorkspace->getConfigName());\n        return {};\n    }\n\n    static auto PBACKANDFORTH = CConfigValue<Hyprlang::INT>(\"binds:workspace_back_and_forth\");\n    const auto  PREVWS        = Desktop::History::workspaceTracker()->previousWorkspaceIDName(pWorkspace);\n\n    if (*PBACKANDFORTH && PCURRMONITOR->activeWorkspaceID() == workspaceID && PREVWS.id != -1) {\n        // Workspace to focus is previous workspace\n        pWorkspace = g_pCompositor->getWorkspaceByID(PREVWS.id);\n        if (!pWorkspace)\n            pWorkspace = g_pCompositor->createNewWorkspace(PREVWS.id, PCURRMONITOR->m_id, PREVWS.name);\n\n        workspaceID = pWorkspace->m_id;\n    }\n\n    if (pWorkspace->m_monitor != PCURRMONITOR) {\n        const auto POLDMONITOR = pWorkspace->m_monitor.lock();\n        if (!POLDMONITOR) { // wat\n            Log::logger->log(Log::ERR, \"focusWorkspaceOnCurrentMonitor old monitor doesn't exist!\");\n            return {.success = false, .error = \"focusWorkspaceOnCurrentMonitor old monitor doesn't exist!\"};\n        }\n        if (POLDMONITOR->activeWorkspaceID() == workspaceID) {\n            g_pCompositor->swapActiveWorkspaces(POLDMONITOR, PCURRMONITOR);\n            return {};\n        } else {\n            g_pCompositor->moveWorkspaceToMonitor(pWorkspace, PCURRMONITOR, true);\n        }\n    }\n\n    changeworkspace(pWorkspace->getConfigName());\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::toggleSpecialWorkspace(std::string args) {\n    const auto& [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(\"special:\" + args);\n    if (workspaceID == WORKSPACE_INVALID || !g_pCompositor->isWorkspaceSpecial(workspaceID)) {\n        Log::logger->log(Log::ERR, \"Invalid workspace passed to special\");\n        return {.success = false, .error = \"Invalid workspace passed to special\"};\n    }\n\n    bool       requestedWorkspaceIsAlreadyOpen = false;\n    const auto PMONITOR                        = Desktop::focusState()->monitor();\n    auto       specialOpenOnMonitor            = PMONITOR->activeSpecialWorkspaceID();\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m->activeSpecialWorkspaceID() == workspaceID) {\n            requestedWorkspaceIsAlreadyOpen = true;\n            break;\n        }\n    }\n\n    updateRelativeCursorCoords();\n\n    PHLWORKSPACEREF focusedWorkspace;\n\n    if (requestedWorkspaceIsAlreadyOpen && specialOpenOnMonitor == workspaceID) {\n        // already open on this monitor\n        Log::logger->log(Log::DEBUG, \"Toggling special workspace {} to closed\", workspaceID);\n        PMONITOR->setSpecialWorkspace(nullptr);\n\n        focusedWorkspace = PMONITOR->m_activeWorkspace;\n    } else {\n        Log::logger->log(Log::DEBUG, \"Toggling special workspace {} to open\", workspaceID);\n        auto PSPECIALWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceID);\n\n        if (!PSPECIALWORKSPACE)\n            PSPECIALWORKSPACE = g_pCompositor->createNewWorkspace(workspaceID, PMONITOR->m_id, workspaceName);\n\n        PMONITOR->setSpecialWorkspace(PSPECIALWORKSPACE);\n\n        focusedWorkspace = PSPECIALWORKSPACE;\n    }\n\n    const static auto PWARPONTOGGLESPECIAL = CConfigValue<Hyprlang::INT>(\"cursor:warp_on_toggle_special\");\n\n    if (*PWARPONTOGGLESPECIAL > 0) {\n        auto PLAST     = focusedWorkspace->getLastFocusedWindow();\n        auto HLSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock());\n\n        if (PLAST && (!HLSurface || HLSurface->view()->type() == Desktop::View::VIEW_TYPE_WINDOW))\n            PLAST->warpCursor(*PWARPONTOGGLESPECIAL == 2);\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::forceRendererReload(std::string args) {\n    bool overAgain = false;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (!m->m_output)\n            continue;\n\n        auto rule = g_pConfigManager->getMonitorRuleFor(m);\n        if (!m->applyMonitorRule(&rule, true)) {\n            overAgain = true;\n            break;\n        }\n    }\n\n    if (overAgain)\n        forceRendererReload(args);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::resizeActive(std::string args) {\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"No window found\"};\n\n    if (PLASTWINDOW->isFullscreen())\n        return {.success = false, .error = \"Window is fullscreen\"};\n\n    const auto SIZ = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realSize->goal());\n\n    if (SIZ.x < 1 || SIZ.y < 1)\n        return {.success = false, .error = \"Invalid size provided\"};\n\n    g_layoutManager->resizeTarget(SIZ - PLASTWINDOW->m_realSize->goal(), PLASTWINDOW->layoutTarget());\n\n    if (PLASTWINDOW->m_realSize->goal().x > 1 && PLASTWINDOW->m_realSize->goal().y > 1)\n        PLASTWINDOW->setHidden(false);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveActive(std::string args) {\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"No window found\"};\n\n    if (PLASTWINDOW->isFullscreen())\n        return {.success = false, .error = \"Window is fullscreen\"};\n\n    const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realPosition->goal());\n\n    g_layoutManager->moveTarget(POS - PLASTWINDOW->m_realPosition->goal(), PLASTWINDOW->layoutTarget());\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveWindow(std::string args) {\n\n    const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1);\n    const auto MOVECMD     = args.substr(0, args.find_first_of(','));\n\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"moveWindow: no window\");\n        return {.success = false, .error = \"moveWindow: no window\"};\n    }\n\n    if (PWINDOW->isFullscreen())\n        return {.success = false, .error = \"Window is fullscreen\"};\n\n    const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal());\n\n    g_layoutManager->moveTarget(POS - PWINDOW->m_realPosition->goal(), PWINDOW->layoutTarget());\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::resizeWindow(std::string args) {\n\n    const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1);\n    const auto MOVECMD     = args.substr(0, args.find_first_of(','));\n\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"resizeWindow: no window\");\n        return {.success = false, .error = \"resizeWindow: no window\"};\n    }\n\n    if (PWINDOW->isFullscreen())\n        return {.success = false, .error = \"Window is fullscreen\"};\n\n    const auto SIZ = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realSize->goal());\n\n    if (SIZ.x < 1 || SIZ.y < 1)\n        return {.success = false, .error = \"Invalid size provided\"};\n\n    g_layoutManager->resizeTarget(SIZ - PWINDOW->m_realSize->goal(), PWINDOW->layoutTarget(), Layout::CORNER_NONE);\n\n    if (PWINDOW->m_realSize->goal().x > 1 && PWINDOW->m_realSize->goal().y > 1)\n        PWINDOW->setHidden(false);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::circleNext(std::string arg) {\n    if (!Desktop::focusState()->window()) {\n        // if we have a clear focus, find the first window and get the next focusable.\n        const auto PWS = Desktop::focusState()->monitor()->m_activeWorkspace;\n        if (PWS && PWS->getWindows() > 0) {\n            const auto PWINDOW = PWS->getFirstWindow();\n            switchToWindow(PWINDOW);\n        }\n\n        return {};\n    }\n\n    CVarList            args{arg, 0, 's', true};\n\n    const auto          PREV = args.contains(\"prev\") || args.contains(\"p\") || args.contains(\"last\") || args.contains(\"l\");\n\n    std::optional<bool> floatStatus = {};\n    if (args.contains(\"tile\") || args.contains(\"tiled\")) {\n        // if we want just tiled, and we are on a tiled window, use layoutmsg for layouts that support it\n\n        if (!Desktop::focusState()->window()->m_isFloating) {\n            if (const auto SPACE = Desktop::focusState()->window()->layoutTarget()->space(); SPACE) {\n\n                constexpr const std::array<const std::type_info*, 2> LAYOUTS_WITH_CYCLE_NEXT = {\n                    &typeid(Layout::Tiled::CMonocleAlgorithm),\n                    &typeid(Layout::Tiled::CMasterAlgorithm),\n                };\n\n                if (std::ranges::contains(LAYOUTS_WITH_CYCLE_NEXT, &typeid(*SPACE->algorithm()->tiledAlgo().get()))) {\n                    CKeybindManager::layoutmsg(PREV ? \"cyclenext, b\" : \"cyclenext\");\n                    return {};\n                }\n            }\n        }\n    }\n\n    if (args.contains(\"float\") || args.contains(\"floating\"))\n        floatStatus = true;\n\n    const auto  VISIBLE = args.contains(\"visible\") || args.contains(\"v\");\n    const auto  NEXT    = args.contains(\"next\") || args.contains(\"n\"); // prev is default in classic alt+tab\n    const auto  HIST    = args.contains(\"hist\") || args.contains(\"h\");\n    const auto& w       = HIST ? g_pCompositor->getWindowCycleHist(Desktop::focusState()->window(), true, floatStatus, VISIBLE, NEXT) :\n                                 g_pCompositor->getWindowCycle(Desktop::focusState()->window(), true, floatStatus, VISIBLE, PREV);\n\n    switchToWindow(w, HIST);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::focusWindow(std::string regexp) {\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp);\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"No such window found\"};\n\n    Log::logger->log(Log::DEBUG, \"Focusing to window name: {}\", PWINDOW->m_title);\n\n    const auto PWORKSPACE = PWINDOW->m_workspace;\n    if (!PWORKSPACE) {\n        Log::logger->log(Log::ERR, \"BUG THIS: null workspace in focusWindow\");\n        return {.success = false, .error = \"BUG THIS: null workspace in focusWindow\"};\n    }\n\n    updateRelativeCursorCoords();\n\n    if (Desktop::focusState()->monitor() && Desktop::focusState()->monitor()->m_activeWorkspace != PWINDOW->m_workspace &&\n        Desktop::focusState()->monitor()->m_activeSpecialWorkspace != PWINDOW->m_workspace) {\n        Log::logger->log(Log::DEBUG, \"Fake executing workspace to move focus\");\n        changeworkspace(PWORKSPACE->getConfigName());\n    }\n\n    Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND, nullptr, false);\n\n    PWINDOW->warpCursor();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::tagWindow(std::string args) {\n    PHLWINDOW PWINDOW = nullptr;\n    CVarList  vars{args, 0, 's', true};\n\n    if (vars.size() == 1)\n        PWINDOW = Desktop::focusState()->window();\n    else if (vars.size() == 2)\n        PWINDOW = g_pCompositor->getWindowByRegex(vars[1]);\n    else\n        return {.success = false, .error = \"Invalid number of arguments, expected 1 or 2 arguments\"};\n\n    if (PWINDOW && PWINDOW->m_ruleApplicator->m_tagKeeper.applyTag(vars[0])) {\n        PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TAG);\n        PWINDOW->updateDecorationValues();\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::toggleSwallow(std::string args) {\n    PHLWINDOWREF pWindow = Desktop::focusState()->window();\n\n    if (!valid(pWindow) || !valid(pWindow->m_swallowed))\n        return {};\n\n    if (pWindow->m_swallowed->m_currentlySwallowed) {\n        // Unswallow\n        pWindow->m_swallowed->m_currentlySwallowed = false;\n        pWindow->m_swallowed->setHidden(false);\n        g_layoutManager->newTarget(pWindow->m_swallowed->layoutTarget(), pWindow->m_workspace->m_space);\n    } else {\n        // Reswallow\n        pWindow->m_swallowed->m_currentlySwallowed = true;\n        pWindow->m_swallowed->setHidden(true);\n        g_layoutManager->removeTarget(pWindow->m_swallowed->layoutTarget());\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::setSubmap(std::string submap) {\n    if (submap == \"reset\" || submap.empty()) {\n        m_currentSelectedSubmap.name = \"\";\n        Log::logger->log(Log::DEBUG, \"Reset active submap to the default one.\");\n        g_pEventManager->postEvent(SHyprIPCEvent{\"submap\", \"\"});\n        Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name);\n        return {};\n    }\n\n    for (const auto& k : g_pKeybindManager->m_keybinds) {\n        if (k->submap.name == submap) {\n            m_currentSelectedSubmap.name = submap;\n            Log::logger->log(Log::DEBUG, \"Changed keybind submap to {}\", submap);\n            g_pEventManager->postEvent(SHyprIPCEvent{\"submap\", submap});\n            Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name);\n            return {};\n        }\n    }\n\n    Log::logger->log(Log::ERR, \"Cannot set submap {}, submap doesn't exist (wasn't registered!)\", submap);\n    return {.success = false, .error = std::format(\"Cannot set submap {}, submap doesn't exist (wasn't registered!)\", submap)};\n}\n\nSDispatchResult CKeybindManager::pass(std::string regexp) {\n\n    // find the first window passing the regex\n    const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp);\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"pass: window not found\");\n        return {.success = false, .error = \"pass: window not found\"};\n    }\n\n    if (!g_pSeatManager->m_keyboard) {\n        Log::logger->log(Log::ERR, \"No kb in pass?\");\n        return {.success = false, .error = \"No kb in pass?\"};\n    }\n\n    const auto XWTOXW        = PWINDOW->m_isX11 && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isX11;\n    const auto LASTMOUSESURF = g_pSeatManager->m_state.pointerFocus.lock();\n    const auto LASTKBSURF    = g_pSeatManager->m_state.keyboardFocus.lock();\n\n    // pass all mf shit\n    if (!XWTOXW) {\n        if (g_pKeybindManager->m_lastCode != 0)\n            g_pSeatManager->setKeyboardFocus(PWINDOW->wlSurface()->resource());\n        else\n            g_pSeatManager->setPointerFocus(PWINDOW->wlSurface()->resource(), {1, 1});\n    }\n\n    g_pSeatManager->sendKeyboardMods(g_pInputManager->getModsFromAllKBs(), 0, 0, 0);\n\n    if (g_pKeybindManager->m_passPressed == 1) {\n        if (g_pKeybindManager->m_lastCode != 0)\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastCode - 8, WL_KEYBOARD_KEY_STATE_PRESSED);\n        else\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastMouseCode, WL_POINTER_BUTTON_STATE_PRESSED);\n    } else if (g_pKeybindManager->m_passPressed == 0)\n        if (g_pKeybindManager->m_lastCode != 0)\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastCode - 8, WL_KEYBOARD_KEY_STATE_RELEASED);\n        else\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastMouseCode, WL_POINTER_BUTTON_STATE_RELEASED);\n    else {\n        // dynamic call of the dispatcher\n        if (g_pKeybindManager->m_lastCode != 0) {\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastCode - 8, WL_KEYBOARD_KEY_STATE_PRESSED);\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastCode - 8, WL_KEYBOARD_KEY_STATE_RELEASED);\n        } else {\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastMouseCode, WL_POINTER_BUTTON_STATE_PRESSED);\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, g_pKeybindManager->m_lastMouseCode, WL_POINTER_BUTTON_STATE_RELEASED);\n        }\n    }\n\n    if (XWTOXW)\n        return {};\n\n    // Massive hack:\n    // this will make g_pSeatManager NOT send the leave event to XWayland apps, provided we are not on an XWayland window already.\n    // please kill me\n    if (PWINDOW->m_isX11) {\n        if (g_pKeybindManager->m_lastCode != 0) {\n            g_pSeatManager->m_state.keyboardFocus.reset();\n            g_pSeatManager->m_state.keyboardFocusResource.reset();\n        } else {\n            g_pSeatManager->m_state.pointerFocus.reset();\n            g_pSeatManager->m_state.pointerFocusResource.reset();\n        }\n    }\n\n    const auto SL = PWINDOW->m_realPosition->goal() - g_pInputManager->getMouseCoordsInternal();\n\n    if (g_pKeybindManager->m_lastCode != 0)\n        g_pSeatManager->setKeyboardFocus(LASTKBSURF);\n    else\n        g_pSeatManager->setPointerFocus(LASTMOUSESURF, SL);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::sendshortcut(std::string args) {\n    // args=<NEW_MODKEYS><NEW_KEY>[,WINDOW_RULES]\n    const auto ARGS = CVarList(args, 3);\n    if (ARGS.size() != 3) {\n        Log::logger->log(Log::ERR, \"sendshortcut: invalid args\");\n        return {.success = false, .error = \"sendshortcut: invalid args\"};\n    }\n\n    const auto MOD     = g_pKeybindManager->stringToModMask(ARGS[0]);\n    const auto KEY     = ARGS[1];\n    uint32_t   keycode = 0;\n    bool       isMouse = false;\n\n    // similar to parseKey in ConfigManager\n    if (isNumber(KEY) && std::stoi(KEY) > 9)\n        keycode = std::stoi(KEY);\n    else if (KEY.compare(0, 5, \"code:\") == 0 && isNumber(KEY.substr(5)))\n        keycode = std::stoi(KEY.substr(5));\n    else if (KEY.compare(0, 6, \"mouse:\") == 0 && isNumber(KEY.substr(6))) {\n        keycode = std::stoi(KEY.substr(6));\n        isMouse = true;\n        if (keycode < 272) {\n            Log::logger->log(Log::ERR, \"sendshortcut: invalid mouse button\");\n            return {.success = false, .error = \"sendshortcut: invalid mouse button\"};\n        }\n    } else {\n\n        // here, we need to find the keycode from the key name\n        // this is not possible through xkb's lib, so we need to iterate through all keycodes\n        // once found, we save it to the cache\n\n        const auto KEYSYM = xkb_keysym_from_name(KEY.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);\n        keycode           = 0;\n\n        const auto KB = g_pSeatManager->m_keyboard;\n\n        if (!KB) {\n            Log::logger->log(Log::ERR, \"sendshortcut: no kb\");\n            return {.success = false, .error = \"sendshortcut: no kb\"};\n        }\n\n        const auto KEYPAIRSTRING = std::format(\"{}{}\", rc<uintptr_t>(KB.get()), KEY);\n\n        if (!g_pKeybindManager->m_keyToCodeCache.contains(KEYPAIRSTRING)) {\n            xkb_keymap*   km = KB->m_xkbKeymap;\n            xkb_state*    ks = KB->m_xkbState;\n\n            xkb_keycode_t keycode_min, keycode_max;\n            keycode_min = xkb_keymap_min_keycode(km);\n            keycode_max = xkb_keymap_max_keycode(km);\n\n            for (xkb_keycode_t kc = keycode_min; kc <= keycode_max; ++kc) {\n                xkb_keysym_t sym = xkb_state_key_get_one_sym(ks, kc);\n\n                if (sym == KEYSYM) {\n                    keycode                                            = kc;\n                    g_pKeybindManager->m_keyToCodeCache[KEYPAIRSTRING] = keycode;\n                }\n            }\n\n            if (!keycode) {\n                Log::logger->log(Log::ERR, \"sendshortcut: key not found\");\n                return {.success = false, .error = \"sendshortcut: key not found\"};\n            }\n\n        } else\n            keycode = g_pKeybindManager->m_keyToCodeCache[KEYPAIRSTRING];\n    }\n\n    if (!keycode) {\n        Log::logger->log(Log::ERR, \"sendshortcut: invalid key\");\n        return {.success = false, .error = \"sendshortcut: invalid key\"};\n    }\n\n    const std::string regexp      = ARGS[2];\n    PHLWINDOW         PWINDOW     = nullptr;\n    const auto        LASTSURFACE = Desktop::focusState()->surface();\n\n    //if regexp is not empty, send shortcut to current window\n    //else, don't change focus\n    if (!regexp.empty()) {\n        PWINDOW = g_pCompositor->getWindowByRegex(regexp);\n\n        if (!PWINDOW) {\n            Log::logger->log(Log::ERR, \"sendshortcut: window not found\");\n            return {.success = false, .error = \"sendshortcut: window not found\"};\n        }\n\n        if (!g_pSeatManager->m_keyboard) {\n            Log::logger->log(Log::ERR, \"No kb in sendshortcut?\");\n            return {.success = false, .error = \"No kb in sendshortcut?\"};\n        }\n\n        if (!isMouse)\n            g_pSeatManager->setKeyboardFocus(PWINDOW->wlSurface()->resource());\n        else\n            g_pSeatManager->setPointerFocus(PWINDOW->wlSurface()->resource(), {1, 1});\n    }\n\n    //copied the rest from pass and modified it\n    // if wl -> xwl, activate destination\n    if (PWINDOW && PWINDOW->m_isX11 && Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isX11)\n        g_pXWaylandManager->activateSurface(PWINDOW->wlSurface()->resource(), true);\n    // if xwl -> xwl, send to current. Timing issues make this not work.\n    if (PWINDOW && PWINDOW->m_isX11 && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isX11)\n        PWINDOW = nullptr;\n\n    g_pSeatManager->sendKeyboardMods(MOD, 0, 0, 0);\n\n    if (g_pKeybindManager->m_passPressed == 1) {\n        if (!isMouse)\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, keycode - 8, WL_KEYBOARD_KEY_STATE_PRESSED);\n        else\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, keycode, WL_POINTER_BUTTON_STATE_PRESSED);\n    } else if (g_pKeybindManager->m_passPressed == 0) {\n        if (!isMouse)\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, keycode - 8, WL_KEYBOARD_KEY_STATE_RELEASED);\n        else\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, keycode, WL_POINTER_BUTTON_STATE_RELEASED);\n    } else {\n        // dynamic call of the dispatcher\n        if (!isMouse) {\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, keycode - 8, WL_KEYBOARD_KEY_STATE_PRESSED);\n            g_pSeatManager->sendKeyboardKey(g_pKeybindManager->m_timeLastMs, keycode - 8, WL_KEYBOARD_KEY_STATE_RELEASED);\n        } else {\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, keycode, WL_POINTER_BUTTON_STATE_PRESSED);\n            g_pSeatManager->sendPointerButton(g_pKeybindManager->m_timeLastMs, keycode, WL_POINTER_BUTTON_STATE_RELEASED);\n        }\n    }\n\n    g_pSeatManager->sendKeyboardMods(0, 0, 0, 0);\n\n    if (!PWINDOW)\n        return {};\n\n    if (PWINDOW->m_isX11) { //xwayland hack, see pass\n        if (!isMouse) {\n            g_pSeatManager->m_state.keyboardFocus.reset();\n            g_pSeatManager->m_state.keyboardFocusResource.reset();\n        } else {\n            g_pSeatManager->m_state.pointerFocus.reset();\n            g_pSeatManager->m_state.pointerFocusResource.reset();\n        }\n    }\n\n    const auto SL = PWINDOW->m_realPosition->goal() - g_pInputManager->getMouseCoordsInternal();\n\n    if (!isMouse)\n        g_pSeatManager->setKeyboardFocus(LASTSURFACE);\n    else\n        g_pSeatManager->setPointerFocus(LASTSURFACE, SL);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::layoutmsg(std::string msg) {\n    auto ret = g_layoutManager->layoutMsg(msg);\n    if (!ret)\n        return {.success = false, .error = ret.error()};\n    return {};\n}\n\nSDispatchResult CKeybindManager::dpms(std::string arg) {\n    SDispatchResult res;\n    bool            enable = arg.starts_with(\"on\");\n    std::string     port   = \"\";\n\n    bool            isToggle = arg.starts_with(\"toggle\");\n    if (arg.find_first_of(' ') != std::string::npos)\n        port = arg.substr(arg.find_first_of(' ') + 1);\n\n    for (auto const& m : g_pCompositor->m_realMonitors) {\n        if (!m->m_enabled)\n            continue;\n\n        if (!port.empty() && m->m_name != port)\n            continue;\n\n        if (isToggle)\n            enable = !m->m_dpmsStatus;\n\n        m->setDPMS(enable);\n    }\n\n    g_pCompositor->m_dpmsStateOn = enable;\n\n    g_pPointerManager->recheckEnteredOutputs();\n\n    return res;\n}\n\nSDispatchResult CKeybindManager::swapnext(std::string arg) {\n\n    PHLWINDOW toSwap = nullptr;\n\n    if (!Desktop::focusState()->window())\n        return {};\n\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    const auto PLASTCYCLED =\n        validMapped(Desktop::focusState()->window()->m_lastCycledWindow) && Desktop::focusState()->window()->m_lastCycledWindow->m_workspace == PLASTWINDOW->m_workspace ?\n        Desktop::focusState()->window()->m_lastCycledWindow.lock() :\n        nullptr;\n\n    const bool NEED_PREV = arg == \"last\" || arg == \"l\" || arg == \"prev\" || arg == \"p\";\n    toSwap               = g_pCompositor->getWindowCycle(PLASTCYCLED ? PLASTCYCLED : PLASTWINDOW, true, std::nullopt, false, NEED_PREV);\n\n    // sometimes we may come back to ourselves.\n    if (toSwap == PLASTWINDOW)\n        toSwap = g_pCompositor->getWindowCycle(PLASTWINDOW, true, std::nullopt, false, NEED_PREV);\n\n    g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), toSwap->layoutTarget(), false);\n\n    PLASTWINDOW->m_lastCycledWindow = toSwap;\n\n    Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::swapActiveWorkspaces(std::string args) {\n    const auto MON1 = args.substr(0, args.find_first_of(' '));\n    const auto MON2 = args.substr(args.find_first_of(' ') + 1);\n\n    const auto PMON1 = g_pCompositor->getMonitorFromString(MON1);\n    const auto PMON2 = g_pCompositor->getMonitorFromString(MON2);\n\n    if (!PMON1 || !PMON2)\n        return {.success = false, .error = \"No such monitor found\"};\n\n    if (PMON1 == PMON2)\n        return {};\n\n    g_pCompositor->swapActiveWorkspaces(PMON1, PMON2);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::pinActive(std::string args) {\n\n    PHLWINDOW PWINDOW = nullptr;\n\n    if (args != \"active\" && args.length() > 1)\n        PWINDOW = g_pCompositor->getWindowByRegex(args);\n    else\n        PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"pin: window not found\");\n        return {.success = false, .error = \"pin: window not found\"};\n    }\n\n    if (!PWINDOW->m_isFloating || PWINDOW->isFullscreen())\n        return {.success = false, .error = \"Window does not qualify to be pinned\"};\n\n    PWINDOW->m_pinned = !PWINDOW->m_pinned;\n\n    const auto PMONITOR = PWINDOW->m_monitor.lock();\n\n    if (!PMONITOR) {\n        Log::logger->log(Log::ERR, \"pin: monitor not found\");\n        return {.success = false, .error = \"pin: window not found\"};\n    }\n\n    PWINDOW->moveToWorkspace(PMONITOR->m_activeWorkspace);\n\n    PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED);\n\n    const auto PWORKSPACE = PWINDOW->m_workspace;\n\n    PWORKSPACE->m_lastFocusedWindow =\n        g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"pin\", std::format(\"{:x},{}\", rc<uintptr_t>(PWINDOW.get()), sc<int>(PWINDOW->m_pinned))});\n    Event::bus()->m_events.window.pin.emit(PWINDOW);\n\n    g_pHyprRenderer->damageWindow(PWINDOW, true);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::mouse(std::string args) {\n    const auto ARGS    = CVarList(args.substr(1), 2, ' ');\n    const auto PRESSED = args[0] == '1';\n\n    if (!PRESSED) {\n        return changeMouseBindMode(MBIND_INVALID);\n    }\n\n    if (ARGS[0] == \"movewindow\") {\n        return changeMouseBindMode(MBIND_MOVE);\n    } else {\n        try {\n            switch (std::stoi(ARGS[1])) {\n                case 1: return changeMouseBindMode(MBIND_RESIZE_FORCE_RATIO); break;\n                case 2: return changeMouseBindMode(MBIND_RESIZE_BLOCK_RATIO); break;\n                default: return changeMouseBindMode(MBIND_RESIZE);\n            }\n        } catch (std::exception& e) { return changeMouseBindMode(MBIND_RESIZE); }\n    }\n}\n\nSDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) {\n    if (MODE != MBIND_INVALID) {\n        if (g_layoutManager->dragController()->target())\n            return {};\n\n        const auto      MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();\n        const PHLWINDOW PWINDOW = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n\n        if (!PWINDOW)\n            return SDispatchResult{.passEvent = true};\n\n        if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) {\n            if (PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS))\n                return SDispatchResult{.passEvent = false};\n        }\n\n        g_layoutManager->beginDragTarget(PWINDOW->layoutTarget(), MODE);\n    } else {\n        if (!g_layoutManager->dragController()->target())\n            return {};\n\n        g_layoutManager->endDragTarget();\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::bringActiveToTop(std::string args) {\n    if (Desktop::focusState()->window() && Desktop::focusState()->window()->m_isFloating)\n        g_pCompositor->changeWindowZOrder(Desktop::focusState()->window(), true);\n\n    g_pInputManager->simulateMouseMovement();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::alterZOrder(std::string args) {\n    const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1);\n    const auto POSITION    = args.substr(0, args.find_first_of(','));\n    auto       PWINDOW     = g_pCompositor->getWindowByRegex(WINDOWREGEX);\n\n    if (!PWINDOW && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isFloating)\n        PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW) {\n        Log::logger->log(Log::ERR, \"alterZOrder: no window\");\n        return {.success = false, .error = \"alterZOrder: no window\"};\n    }\n\n    if (POSITION == \"top\")\n        g_pCompositor->changeWindowZOrder(PWINDOW, true);\n    else if (POSITION == \"bottom\")\n        g_pCompositor->changeWindowZOrder(PWINDOW, false);\n    else {\n        Log::logger->log(Log::ERR, \"alterZOrder: bad position: {}\", POSITION);\n        return {.success = false, .error = \"alterZOrder: bad position: {}\"};\n    }\n\n    g_pInputManager->simulateMouseMovement();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::lockGroups(std::string args) {\n    if (args == \"lock\" || args.empty() || args == \"lockgroups\")\n        g_pKeybindManager->m_groupsLocked = true;\n    else if (args == \"toggle\")\n        g_pKeybindManager->m_groupsLocked = !g_pKeybindManager->m_groupsLocked;\n    else\n        g_pKeybindManager->m_groupsLocked = false;\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"lockgroups\", g_pKeybindManager->m_groupsLocked ? \"1\" : \"0\"});\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::lockActiveGroup(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"No window found\"};\n\n    if (!PWINDOW->m_group)\n        return {.success = false, .error = \"Not a group\"};\n\n    if (args == \"lock\")\n        PWINDOW->m_group->setLocked(true);\n    else if (args == \"toggle\")\n        PWINDOW->m_group->setLocked(!PWINDOW->m_group->locked());\n    else\n        PWINDOW->m_group->setLocked(false);\n\n    PWINDOW->updateDecorationValues();\n\n    return {};\n}\n\nvoid CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) {\n    if (!pWindowInDirection->m_group || pWindowInDirection->m_group->denied())\n        return;\n\n    updateRelativeCursorCoords();\n\n    if (pWindow->m_monitor != pWindowInDirection->m_monitor) {\n        pWindow->moveToWorkspace(pWindowInDirection->m_workspace);\n        pWindow->m_monitor = pWindowInDirection->m_monitor;\n    }\n\n    pWindowInDirection->m_group->add(pWindow);\n\n    pWindowInDirection->m_group->setCurrent(pWindow);\n    pWindow->updateWindowDecos();\n    Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);\n    pWindow->warpCursor();\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"moveintogroup\", std::format(\"{:x}\", rc<uintptr_t>(pWindow.get()))});\n}\n\nvoid CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir) {\n    static auto BFOCUSREMOVEDWINDOW = CConfigValue<Hyprlang::INT>(\"group:focus_removed_window\");\n\n    if (!pWindow->m_group)\n        return;\n\n    WP<Desktop::View::CGroup> group = pWindow->m_group;\n\n    const auto                direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT;\n\n    pWindow->m_group->remove(pWindow, direction);\n\n    if (*BFOCUSREMOVEDWINDOW || !group) {\n        Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);\n        pWindow->warpCursor();\n    } else {\n        Desktop::focusState()->fullWindowFocus(group->current(), Desktop::FOCUS_REASON_KEYBIND);\n        group->current()->warpCursor();\n    }\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"moveoutofgroup\", std::format(\"{:x}\", rc<uintptr_t>(pWindow.get()))});\n}\n\nSDispatchResult CKeybindManager::moveIntoGroup(std::string args) {\n    static auto PIGNOREGROUPLOCK = CConfigValue<Hyprlang::INT>(\"binds:ignore_group_lock\");\n\n    if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked)\n        return {};\n\n    Math::eDirection dir = Math::fromChar(args[0]);\n    if (dir == Math::DIRECTION_DEFAULT) {\n        Log::logger->log(Log::ERR, \"Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0]);\n        return {.success = false, .error = std::format(\"Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0])};\n    }\n\n    const auto PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {};\n\n    auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir);\n\n    if (!PWINDOWINDIR || !PWINDOWINDIR->m_group)\n        return {};\n\n    const auto GROUP = PWINDOWINDIR->m_group;\n\n    // Do not move window into locked group if binds:ignore_group_lock is false\n    if (!*PIGNOREGROUPLOCK && (GROUP->locked() || (PWINDOW->m_group && PWINDOW->m_group->locked())))\n        return {};\n\n    moveWindowIntoGroup(PWINDOW, PWINDOWINDIR);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveOutOfGroup(std::string args) {\n    static auto PIGNOREGROUPLOCK = CConfigValue<Hyprlang::INT>(\"binds:ignore_group_lock\");\n\n    if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked)\n        return {.success = false, .error = \"Groups locked\"};\n\n    PHLWINDOW PWINDOW = nullptr;\n\n    if (args != \"active\" && args.length() > 1)\n        PWINDOW = g_pCompositor->getWindowByRegex(args);\n    else\n        PWINDOW = Desktop::focusState()->window();\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"No window found\"};\n\n    if (!PWINDOW->m_group)\n        return {.success = false, .error = \"Window not in a group\"};\n\n    moveWindowOutOfGroup(PWINDOW);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) {\n    static auto      PIGNOREGROUPLOCK = CConfigValue<Hyprlang::INT>(\"binds:ignore_group_lock\");\n\n    Math::eDirection dir = Math::fromChar(args[0]);\n    if (dir == Math::DIRECTION_DEFAULT) {\n        Log::logger->log(Log::ERR, \"Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0]);\n        return {.success = false, .error = std::format(\"Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b\", args[0])};\n    }\n\n    const auto PWINDOW = Desktop::focusState()->window();\n    if (!PWINDOW)\n        return {.success = false, .error = \"No window found\"};\n\n    if (PWINDOW->isFullscreen())\n        return {};\n\n    if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) {\n        g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);\n        return {};\n    }\n\n    const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir);\n\n    const bool ISWINDOWGROUP       = PWINDOW->m_group;\n    const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->m_group->locked();\n    const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_group->size() == 1;\n    const bool ISWINDOWGROUPDENIED = ISWINDOWGROUP && PWINDOW->m_group->denied();\n\n    updateRelativeCursorCoords();\n\n    // note: PWINDOWINDIR is not null implies !PWINDOW->m_isFloating\n    if (PWINDOWINDIR && PWINDOWINDIR->m_group) { // target is group\n        if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->m_group->locked() || ISWINDOWGROUPLOCKED || ISWINDOWGROUPDENIED)) {\n            g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);\n            PWINDOW->warpCursor();\n        } else\n            moveWindowIntoGroup(PWINDOW, PWINDOWINDIR);\n    } else if (PWINDOWINDIR) { // target is regular window\n        if ((!*PIGNOREGROUPLOCK && ISWINDOWGROUPLOCKED) || !ISWINDOWGROUP || (ISWINDOWGROUPSINGLE && PWINDOW->m_groupRules & Desktop::View::GROUP_SET_ALWAYS)) {\n            g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);\n            PWINDOW->warpCursor();\n        } else\n            moveWindowOutOfGroup(PWINDOW, args);\n    } else if ((*PIGNOREGROUPLOCK || !ISWINDOWGROUPLOCKED) && ISWINDOWGROUP) { // no target window\n        moveWindowOutOfGroup(PWINDOW, args);\n    } else if (!PWINDOWINDIR && !ISWINDOWGROUP) { // no target in dir and not in group\n        g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args);\n        PWINDOW->warpCursor();\n    }\n\n    PWINDOW->updateDecorationValues();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) {\n    static auto PIGNOREGROUPLOCK = rc<Hyprlang::INT* const*>(g_pConfigManager->getConfigValuePtr(\"binds:ignore_group_lock\"));\n\n    if (args == \"toggle\")\n        **PIGNOREGROUPLOCK = !**PIGNOREGROUPLOCK;\n    else\n        **PIGNOREGROUPLOCK = args == \"on\";\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"ignoregrouplock\", std::to_string(**PIGNOREGROUPLOCK)});\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) {\n    const auto PWINDOW = Desktop::focusState()->window();\n    if (!PWINDOW || (PWINDOW && PWINDOW->m_group))\n        return {};\n\n    if (args == \"toggle\")\n        PWINDOW->m_group->setDenied(!PWINDOW->m_group->denied());\n    else\n        PWINDOW->m_group->setDenied(args == \"on\");\n\n    PWINDOW->updateDecorationValues();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::global(std::string args) {\n    const auto APPID = args.substr(0, args.find_first_of(':'));\n    const auto NAME  = args.substr(args.find_first_of(':') + 1);\n\n    if (NAME.empty())\n        return {};\n\n    if (!PROTO::globalShortcuts->isTaken(APPID, NAME))\n        return {};\n\n    PROTO::globalShortcuts->sendGlobalShortcutEvent(APPID, NAME, g_pKeybindManager->m_passPressed);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::moveGroupWindow(std::string args) {\n    const auto BACK = args == \"b\" || args == \"prev\";\n\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n\n    if (!PLASTWINDOW)\n        return {.success = false, .error = \"No window found\"};\n\n    if (!PLASTWINDOW->m_group)\n        return {.success = false, .error = \"Window not in a group\"};\n\n    const auto GROUP = PLASTWINDOW->m_group;\n\n    if (BACK)\n        GROUP->swapWithLast();\n    else\n        GROUP->swapWithNext();\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::event(std::string args) {\n    g_pEventManager->postEvent(SHyprIPCEvent{\"custom\", args});\n    return {};\n}\n\n#include <utility>\n#include <type_traits>\n\ntemplate <typename T>\nstatic void parsePropTrivial(Desktop::Types::COverridableVar<T>& prop, const std::string& s) {\n    static_assert(std::is_same_v<T, bool> || std::is_same_v<T, Hyprlang::INT> || std::is_same_v<T, int> || std::is_same_v<T, Hyprlang::FLOAT> || std::is_same_v<T, std::string>,\n                  \"Invalid type passed to parsePropTrivial\");\n\n    if (s == \"unset\") {\n        prop.unset(Desktop::Types::PRIORITY_SET_PROP);\n        return;\n    }\n\n    try {\n        if constexpr (std::is_same_v<T, bool>) {\n            if (s == \"toggle\")\n                prop.increment(true, Desktop::Types::PRIORITY_SET_PROP);\n            else\n                prop = Desktop::Types::COverridableVar<T>(truthy(s), Desktop::Types::PRIORITY_SET_PROP);\n        } else if constexpr (std::is_same_v<T, Hyprlang::INT> || std::is_same_v<T, int>) {\n            if (s.starts_with(\"relative\")) {\n                const auto VAL = std::stoi(s.substr(s.find(' ') + 1));\n                prop.increment(VAL, Desktop::Types::PRIORITY_SET_PROP);\n            } else\n                prop = Desktop::Types::COverridableVar<T>(std::stoull(s), Desktop::Types::PRIORITY_SET_PROP);\n        } else if constexpr (std::is_same_v<T, Hyprlang::FLOAT>) {\n            if (s.starts_with(\"relative\")) {\n                const auto VAL = std::stof(s.substr(s.find(' ') + 1));\n                prop.increment(VAL, Desktop::Types::PRIORITY_SET_PROP);\n            } else\n                prop = Desktop::Types::COverridableVar<T>(std::stof(s), Desktop::Types::PRIORITY_SET_PROP);\n        } else if constexpr (std::is_same_v<T, std::string>)\n            prop = Desktop::Types::COverridableVar<T>(s, Desktop::Types::PRIORITY_SET_PROP);\n    } catch (...) { Log::logger->log(Log::ERR, \"Hyprctl: parsePropTrivial: failed to parse setprop for {}\", s); }\n}\n\nSDispatchResult CKeybindManager::setProp(std::string args) {\n    CVarList vars(args, 3, ' ');\n\n    if (vars.size() < 3)\n        return {.success = false, .error = \"Not enough args\"};\n\n    const auto PLASTWINDOW = Desktop::focusState()->window();\n    const auto PWINDOW     = g_pCompositor->getWindowByRegex(vars[0]);\n\n    if (!PWINDOW)\n        return {.success = false, .error = \"Window not found\"};\n\n    const auto PROP = vars[1];\n    const auto VAL  = vars[2];\n\n    bool       noFocus = PWINDOW->m_ruleApplicator->noFocus().valueOrDefault();\n\n    try {\n        if (PROP == \"max_size\") {\n            const auto SIZE = PWINDOW->calculateExpression(VAL);\n            if (!SIZE) {\n                Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", VAL);\n                throw \"failed to parse expression\";\n            }\n            PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP));\n            PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value());\n            PWINDOW->setHidden(false);\n        } else if (PROP == \"min_size\") {\n            const auto SIZE = PWINDOW->calculateExpression(VAL);\n            if (!SIZE) {\n                Log::logger->log(Log::ERR, \"failed to parse {} as an expression\", VAL);\n                throw \"failed to parse expression\";\n            }\n            PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP));\n            PWINDOW->clampWindowSize(PWINDOW->m_ruleApplicator->minSize().value(), std::nullopt);\n            PWINDOW->setHidden(false);\n        } else if (PROP == \"active_border_color\" || PROP == \"inactive_border_color\") {\n            CGradientValueData colorData = {};\n            if (vars.size() > 4) {\n                for (int i = 3; i < sc<int>(vars.size()); ++i) {\n                    const auto TOKEN = vars[i];\n                    if (TOKEN.ends_with(\"deg\"))\n                        colorData.m_angle = std::stoi(TOKEN.substr(0, TOKEN.size() - 3)) * (PI / 180.0);\n                    else\n                        configStringToInt(TOKEN).and_then([&colorData](const auto& e) {\n                            colorData.m_colors.push_back(e);\n                            return std::invoke_result_t<decltype(::configStringToInt), const std::string&>(1);\n                        });\n                }\n            } else if (VAL != \"-1\")\n                configStringToInt(VAL).and_then([&colorData](const auto& e) {\n                    colorData.m_colors.push_back(e);\n                    return std::invoke_result_t<decltype(::configStringToInt), const std::string&>(1);\n                });\n\n            colorData.updateColorsOk();\n\n            if (PROP == \"active_border_color\")\n                PWINDOW->m_ruleApplicator->activeBorderColorOverride(Desktop::Types::COverridableVar(colorData, Desktop::Types::PRIORITY_SET_PROP));\n            else\n                PWINDOW->m_ruleApplicator->inactiveBorderColorOverride(Desktop::Types::COverridableVar(colorData, Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"opacity\") {\n            PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar(\n                Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alpha().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"opacity_inactive\") {\n            PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar(\n                Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"opacity_fullscreen\") {\n            PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar(\n                Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"opacity_override\") {\n            PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar(\n                Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alpha().valueOrDefault().alpha, sc<bool>(configStringToInt(VAL).value_or(0))},\n                Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"opacity_inactive_override\") {\n            PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar(\n                Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().alpha, sc<bool>(configStringToInt(VAL).value_or(0))},\n                Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"opacity_fullscreen_override\") {\n            PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar(\n                Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().alpha, sc<bool>(configStringToInt(VAL).value_or(0))},\n                Desktop::Types::PRIORITY_SET_PROP));\n        } else if (PROP == \"allows_input\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->allowsInput(), VAL);\n        else if (PROP == \"decorate\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->decorate(), VAL);\n        else if (PROP == \"focus_on_activate\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->focusOnActivate(), VAL);\n        else if (PROP == \"keep_aspect_ratio\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->keepAspectRatio(), VAL);\n        else if (PROP == \"nearest_neighbor\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->nearestNeighbor(), VAL);\n        else if (PROP == \"no_anim\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noAnim(), VAL);\n        else if (PROP == \"no_blur\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noBlur(), VAL);\n        else if (PROP == \"no_dim\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noDim(), VAL);\n        else if (PROP == \"no_focus\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noFocus(), VAL);\n        else if (PROP == \"no_max_size\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noMaxSize(), VAL);\n        else if (PROP == \"no_shadow\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noShadow(), VAL);\n        else if (PROP == \"no_shortcuts_inhibit\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noShortcutsInhibit(), VAL);\n        else if (PROP == \"dim_around\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->dimAround(), VAL);\n        else if (PROP == \"opaque\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->opaque(), VAL);\n        else if (PROP == \"force_rgbx\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->RGBX(), VAL);\n        else if (PROP == \"sync_fullscreen\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->syncFullscreen(), VAL);\n        else if (PROP == \"immediate\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->tearing(), VAL);\n        else if (PROP == \"xray\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->xray(), VAL);\n        else if (PROP == \"render_unfocused\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->renderUnfocused(), VAL);\n        else if (PROP == \"no_follow_mouse\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noFollowMouse(), VAL);\n        else if (PROP == \"no_screen_share\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noScreenShare(), VAL);\n        else if (PROP == \"no_vrr\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->noVRR(), VAL);\n        else if (PROP == \"persistent_size\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->persistentSize(), VAL);\n        else if (PROP == \"stay_focused\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->stayFocused(), VAL);\n        else if (PROP == \"idle_inhibit\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->idleInhibitMode(), VAL);\n        else if (PROP == \"border_size\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->borderSize(), VAL);\n        else if (PROP == \"rounding\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->rounding(), VAL);\n        else if (PROP == \"rounding_power\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->roundingPower(), VAL);\n        else if (PROP == \"scroll_mouse\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->scrollMouse(), VAL);\n        else if (PROP == \"scroll_touchpad\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->scrollTouchpad(), VAL);\n        else if (PROP == \"animation\")\n            parsePropTrivial(PWINDOW->m_ruleApplicator->animationStyle(), VAL);\n        else\n            return {.success = false, .error = \"prop not found\"};\n\n    } catch (std::exception& e) { return {.success = false, .error = std::format(\"Error parsing prop value: {}\", std::string(e.what()))}; }\n\n    g_pCompositor->updateAllWindowsAnimatedDecorationValues();\n\n    if (PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() != noFocus) {\n        // FIXME: what the fuck is going on here? -vax\n        Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND);\n        Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND);\n        Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND);\n    }\n\n    if (PROP == \"no_vrr\")\n        g_pConfigManager->ensureVRR(PWINDOW->m_monitor.lock());\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m->m_activeWorkspace)\n            m->m_activeWorkspace->m_space->recalculate();\n    }\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::forceIdle(std::string args) {\n    std::optional<float> duration = getPlusMinusKeywordResult(args, 0);\n\n    if (!duration.has_value()) {\n        Log::logger->log(Log::ERR, \"Duration invalid in forceIdle!\");\n        return {.success = false, .error = \"Duration invalid in forceIdle!\"};\n    }\n\n    PROTO::idle->setTimers(duration.value() * 1000.0);\n\n    return {};\n}\n\nSDispatchResult CKeybindManager::sendkeystate(std::string args) {\n    // args=<NEW_MODKEYS><NEW_KEY><STATE>[,WINDOW_RULES]\n    const auto ARGS = CVarList(args, 4);\n    if (ARGS.size() != 4) {\n        Log::logger->log(Log::ERR, \"sendkeystate: invalid args\");\n        return {.success = false, .error = \"sendkeystate: invalid args\"};\n    }\n\n    const auto STATE = ARGS[2];\n\n    if (STATE != \"down\" && STATE != \"repeat\" && STATE != \"up\") {\n        Log::logger->log(Log::ERR, \"sendkeystate: invalid state, must be 'down', 'repeat', or 'up'\");\n        return {.success = false, .error = \"sendkeystate: invalid state, must be 'down', 'repeat', or 'up'\"};\n    }\n\n    std::string modifiedArgs = ARGS[0] + \",\" + ARGS[1] + \",\" + ARGS[3];\n\n    const int   oldPassPressed = g_pKeybindManager->m_passPressed;\n\n    if (STATE == \"down\")\n        g_pKeybindManager->m_passPressed = 1;\n    else if (STATE == \"up\")\n        g_pKeybindManager->m_passPressed = 0;\n    else if (STATE == \"repeat\")\n        g_pKeybindManager->m_passPressed = 1;\n\n    auto result = sendshortcut(modifiedArgs);\n\n    if (STATE == \"repeat\" && result.success)\n        result = sendshortcut(modifiedArgs);\n\n    g_pKeybindManager->m_passPressed = oldPassPressed;\n\n    if (!result.success && !result.error.empty()) {\n        size_t pos = result.error.find(\"sendshortcut:\");\n        if (pos != std::string::npos)\n            result.error = \"sendkeystate:\" + result.error.substr(pos + 13);\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "src/managers/KeybindManager.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include <vector>\n#include <set>\n#include <unordered_set>\n#include <unordered_map>\n#include <functional>\n#include <xkbcommon/xkbcommon.h>\n#include \"../devices/IPointer.hpp\"\n#include \"eventLoop/EventLoopTimer.hpp\"\n#include \"../helpers/time/Timer.hpp\"\n\nclass CInputManager;\nclass CConfigManager;\nclass CPluginSystem;\nclass IKeyboard;\n\nenum eMouseBindMode : int8_t;\n\nstruct SSubmap {\n    std::string name  = \"\";\n    std::string reset = \"\";\n    bool        operator==(const SSubmap& other) const {\n        return name == other.name;\n    }\n};\n\nstruct SKeybind {\n    std::string                     key             = \"\";\n    std::set<xkb_keysym_t>          sMkKeys         = {};\n    uint32_t                        keycode         = 0;\n    bool                            catchAll        = false;\n    uint32_t                        modmask         = 0;\n    std::set<xkb_keysym_t>          sMkMods         = {};\n    std::string                     handler         = \"\";\n    std::string                     arg             = \"\";\n    bool                            locked          = false;\n    SSubmap                         submap          = {};\n    std::string                     description     = \"\";\n    bool                            release         = false;\n    bool                            repeat          = false;\n    bool                            longPress       = false;\n    bool                            mouse           = false;\n    bool                            nonConsuming    = false;\n    bool                            transparent     = false;\n    bool                            ignoreMods      = false;\n    bool                            multiKey        = false;\n    bool                            hasDescription  = false;\n    bool                            dontInhibit     = false;\n    bool                            click           = false;\n    bool                            drag            = false;\n    bool                            submapUniversal = false;\n    bool                            deviceInclusive = false;\n    std::unordered_set<std::string> devices         = {};\n\n    // DO NOT INITIALIZE\n    bool shadowed = false;\n};\n\nenum eFocusWindowMode : uint8_t {\n    MODE_CLASS_REGEX = 0,\n    MODE_INITIAL_CLASS_REGEX,\n    MODE_TITLE_REGEX,\n    MODE_INITIAL_TITLE_REGEX,\n    MODE_TAG_REGEX,\n    MODE_ADDRESS,\n    MODE_PID,\n    MODE_ACTIVE_WINDOW\n};\n\nstruct SPressedKeyWithMods {\n    std::string  keyName            = \"\";\n    xkb_keysym_t keysym             = 0;\n    uint32_t     keycode            = 0;\n    uint32_t     modmaskAtPressTime = 0;\n    bool         sent               = false;\n    SSubmap      submapAtPress      = {};\n    Vector2D     mousePosAtPress    = {};\n};\n\nstruct SParsedKey {\n    std::string key      = \"\";\n    uint32_t    keycode  = 0;\n    bool        catchAll = false;\n};\n\nenum eMultiKeyCase : uint8_t {\n    MK_NO_MATCH = 0,\n    MK_PARTIAL_MATCH,\n    MK_FULL_MATCH\n};\n\nclass CKeybindManager {\n  public:\n    CKeybindManager();\n    ~CKeybindManager();\n\n    bool                                                                         onKeyEvent(std::any, SP<IKeyboard>);\n    bool                                                                         onAxisEvent(const IPointer::SAxisEvent&, SP<IPointer>);\n    bool                                                                         onMouseEvent(const IPointer::SButtonEvent&, SP<IPointer>);\n    void                                                                         resizeWithBorder(const IPointer::SButtonEvent&);\n    void                                                                         onSwitchEvent(const std::string&);\n    void                                                                         onSwitchOnEvent(const std::string&);\n    void                                                                         onSwitchOffEvent(const std::string&);\n\n    void                                                                         addKeybind(SKeybind);\n    void                                                                         removeKeybind(uint32_t, const SParsedKey&);\n    uint32_t                                                                     stringToModMask(std::string);\n    uint32_t                                                                     keycodeToModifier(xkb_keycode_t);\n    void                                                                         clearKeybinds();\n    void                                                                         shadowKeybinds(const xkb_keysym_t& doesntHave = 0, const uint32_t doesntHaveCode = 0);\n    SSubmap                                                                      getCurrentSubmap();\n\n    std::unordered_map<std::string, std::function<SDispatchResult(std::string)>> m_dispatchers;\n\n    bool                                                                         m_groupsLocked = false;\n\n    std::vector<SP<SKeybind>>                                                    m_keybinds;\n\n    //since we can't find keycode through keyname in xkb:\n    //on sendshortcut call, we once search for keyname (e.g. \"g\") the correct keycode (e.g. 42)\n    //and cache it in this map to make sendshortcut calls faster\n    //we also store the keyboard pointer (in the string) to differentiate between different keyboard (layouts)\n    std::unordered_map<std::string, xkb_keycode_t> m_keyToCodeCache;\n\n    static SDispatchResult                         changeMouseBindMode(const eMouseBindMode mode);\n\n  private:\n    std::vector<SPressedKeyWithMods> m_pressedKeys;\n\n    inline static SSubmap            m_currentSelectedSubmap = {};\n\n    std::vector<WP<SKeybind>>        m_activeKeybinds;\n    WP<SKeybind>                     m_lastLongPressKeybind;\n\n    SP<CEventLoopTimer>              m_longPressTimer;\n    SP<CEventLoopTimer>              m_repeatKeyTimer;\n    uint32_t                         m_repeatKeyRate = 50;\n\n    uint32_t                         m_timeLastMs    = 0;\n    uint32_t                         m_lastCode      = 0;\n    uint32_t                         m_lastMouseCode = 0;\n\n    std::vector<WP<SKeybind>>        m_pressedSpecialBinds;\n\n    int                              m_passPressed = -1; // used for pass\n\n    CTimer                           m_scrollTimer;\n\n    SDispatchResult                  handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool, SP<IKeyboard>, SP<IHID>);\n\n    std::set<xkb_keysym_t>           m_mkKeys = {};\n    std::set<xkb_keysym_t>           m_mkMods = {};\n    eMultiKeyCase                    mkBindMatches(const SP<SKeybind>);\n    eMultiKeyCase                    mkKeysymSetMatches(const std::set<xkb_keysym_t>, const std::set<xkb_keysym_t>);\n\n    bool                             handleInternalKeybinds(xkb_keysym_t);\n    bool                             handleVT(xkb_keysym_t);\n\n    xkb_state*                       m_xkbTranslationState = nullptr;\n\n    void                             updateXKBTranslationState();\n    bool                             ensureMouseBindState();\n\n    static bool                      tryMoveFocusToMonitor(PHLMONITOR monitor);\n    static void                      moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir = \"\");\n    static void                      moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection);\n\n    static void                      switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle = false);\n    static uint64_t                  spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken = \"\");\n    static std::optional<uint64_t>   spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace);\n\n    // -------------- Dispatchers -------------- //\n    static SDispatchResult closeActive(std::string);\n    static SDispatchResult killActive(std::string);\n    static SDispatchResult closeWindow(std::string);\n    static SDispatchResult killWindow(std::string);\n    static SDispatchResult signalActive(std::string);\n    static SDispatchResult signalWindow(std::string);\n    static SDispatchResult spawn(std::string);\n    static SDispatchResult spawnRaw(std::string);\n    static SDispatchResult toggleActiveFloating(std::string);\n    static SDispatchResult toggleActivePseudo(std::string);\n    static SDispatchResult setActiveFloating(std::string);\n    static SDispatchResult setActiveTiled(std::string);\n    static SDispatchResult changeworkspace(std::string);\n    static SDispatchResult fullscreenActive(std::string);\n    static SDispatchResult fullscreenStateActive(std::string args);\n    static SDispatchResult moveActiveToWorkspace(std::string);\n    static SDispatchResult moveActiveToWorkspaceSilent(std::string);\n    static SDispatchResult moveFocusTo(std::string);\n    static SDispatchResult focusUrgentOrLast(std::string);\n    static SDispatchResult focusCurrentOrLast(std::string);\n    static SDispatchResult centerWindow(std::string);\n    static SDispatchResult moveActiveTo(std::string);\n    static SDispatchResult swapActive(std::string);\n    static SDispatchResult toggleGroup(std::string);\n    static SDispatchResult changeGroupActive(std::string);\n    static SDispatchResult focusMonitor(std::string);\n    static SDispatchResult moveCursorToCorner(std::string);\n    static SDispatchResult moveCursor(std::string);\n    static SDispatchResult workspaceOpt(std::string);\n    static SDispatchResult renameWorkspace(std::string);\n    static SDispatchResult exitHyprland(std::string);\n    static SDispatchResult moveCurrentWorkspaceToMonitor(std::string);\n    static SDispatchResult moveWorkspaceToMonitor(std::string);\n    static SDispatchResult focusWorkspaceOnCurrentMonitor(std::string);\n    static SDispatchResult toggleSpecialWorkspace(std::string);\n    static SDispatchResult forceRendererReload(std::string);\n    static SDispatchResult resizeActive(std::string);\n    static SDispatchResult moveActive(std::string);\n    static SDispatchResult moveWindow(std::string);\n    static SDispatchResult resizeWindow(std::string);\n    static SDispatchResult circleNext(std::string);\n    static SDispatchResult focusWindow(std::string);\n    static SDispatchResult tagWindow(std::string);\n    static SDispatchResult toggleSwallow(std::string);\n    static SDispatchResult setSubmap(std::string);\n    static SDispatchResult pass(std::string);\n    static SDispatchResult sendshortcut(std::string);\n    static SDispatchResult sendkeystate(std::string);\n    static SDispatchResult layoutmsg(std::string);\n    static SDispatchResult dpms(std::string);\n    static SDispatchResult swapnext(std::string);\n    static SDispatchResult swapActiveWorkspaces(std::string);\n    static SDispatchResult pinActive(std::string);\n    static SDispatchResult mouse(std::string);\n    static SDispatchResult bringActiveToTop(std::string);\n    static SDispatchResult alterZOrder(std::string);\n    static SDispatchResult lockGroups(std::string);\n    static SDispatchResult lockActiveGroup(std::string);\n    static SDispatchResult moveIntoGroup(std::string);\n    static SDispatchResult moveOutOfGroup(std::string);\n    static SDispatchResult moveGroupWindow(std::string);\n    static SDispatchResult moveWindowOrGroup(std::string);\n    static SDispatchResult setIgnoreGroupLock(std::string);\n    static SDispatchResult denyWindowFromGroup(std::string);\n    static SDispatchResult global(std::string);\n    static SDispatchResult event(std::string);\n    static SDispatchResult setProp(std::string);\n    static SDispatchResult forceIdle(std::string);\n\n    friend class CCompositor;\n    friend class CInputManager;\n    friend class CConfigManager;\n    friend class CWorkspace;\n    friend class CPointerManager;\n};\n\ninline UP<CKeybindManager> g_pKeybindManager;\n"
  },
  {
    "path": "src/managers/PointerManager.cpp",
    "content": "#include \"PointerManager.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../protocols/PointerGestures.hpp\"\n#include \"../protocols/RelativePointer.hpp\"\n#include \"../protocols/FractionalScale.hpp\"\n#include \"../protocols/IdleNotify.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../protocols/core/Seat.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"eventLoop/EventLoopManager.hpp\"\n#include \"../render/pass/TexPassElement.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../render/OpenGL.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"SeatManager.hpp\"\n#include \"../helpers/time/Time.hpp\"\n#include \"../helpers/Drm.hpp\"\n#include \"../event/EventBus.hpp\"\n#include <climits>\n#include <cstring>\n#include <gbm.h>\n#include <cairo/cairo.h>\n#include <hyprutils/math/Region.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\n\nusing namespace Hyprutils::Utils;\n\nCPointerManager::CPointerManager() {\n    m_hooks.monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) {\n        onMonitorLayoutChange();\n\n        monitor->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); });\n        monitor->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); });\n        monitor->m_events.destroy.listenStatic([this] {\n            if (g_pCompositor && !g_pCompositor->m_isShuttingDown)\n                std::erase_if(m_monitorStates, [](const auto& other) { return other->monitor.expired(); });\n        });\n    });\n\n    m_hooks.monitorPreRender = Event::bus()->m_events.monitor.preCommit.listen([this](PHLMONITOR monitor) {\n        auto state = stateFor(monitor);\n        if (!state)\n            return;\n\n        state->cursorRendered = false;\n    });\n}\n\nvoid CPointerManager::lockSoftwareAll() {\n    for (auto const& state : m_monitorStates)\n        state->softwareLocks++;\n\n    updateCursorBackend();\n}\n\nvoid CPointerManager::unlockSoftwareAll() {\n    for (auto const& state : m_monitorStates)\n        state->softwareLocks--;\n\n    updateCursorBackend();\n}\n\nvoid CPointerManager::lockSoftwareForMonitor(PHLMONITOR mon) {\n    auto const state = stateFor(mon);\n    state->softwareLocks++;\n\n    if (state->softwareLocks == 1)\n        updateCursorBackend();\n}\n\nvoid CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) {\n    auto const state = stateFor(mon);\n    state->softwareLocks--;\n    if (state->softwareLocks < 0) {\n        state->softwareLocks = 0;\n        Log::logger->log(Log::WARN, \"Unlocking SW for monitor while it's not locked\");\n    }\n\n    if (state->softwareLocks == 0)\n        updateCursorBackend();\n}\n\nbool CPointerManager::softwareLockedFor(PHLMONITOR mon) {\n    auto const state = stateFor(mon);\n    return state->softwareLocks > 0 || (state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor());\n}\n\nbool CPointerManager::hasVisibleHWCursor(PHLMONITOR pMonitor) {\n    auto const state = stateFor(pMonitor);\n    return state->softwareLocks == 0 && !state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor();\n}\n\nVector2D CPointerManager::position() {\n    return m_pointerPos;\n}\n\nVector2D CPointerManager::hotspot() {\n    return m_currentCursorImage.hotspot;\n}\n\nbool CPointerManager::hasCursor() {\n    return m_currentCursorImage.pBuffer || m_currentCursorImage.surface;\n}\n\nSP<CPointerManager::SMonitorPointerState> CPointerManager::stateFor(PHLMONITOR mon) {\n    auto it = std::ranges::find_if(m_monitorStates, [mon](const auto& other) { return other->monitor == mon; });\n    if (it == m_monitorStates.end())\n        return m_monitorStates.emplace_back(makeShared<CPointerManager::SMonitorPointerState>(mon));\n    return *it;\n}\n\nvoid CPointerManager::setCursorBuffer(SP<Aquamarine::IBuffer> buf, const Vector2D& hotspot, const float& scale) {\n    damageIfSoftware();\n    if (buf == m_currentCursorImage.pBuffer) {\n        if (hotspot != m_currentCursorImage.hotspot || scale != m_currentCursorImage.scale) {\n            m_currentCursorImage.hotspot = hotspot;\n            m_currentCursorImage.scale   = scale;\n            updateCursorBackend();\n            damageIfSoftware();\n            m_events.cursorChanged.emit();\n        }\n\n        return;\n    }\n\n    resetCursorImage(false);\n\n    if (buf) {\n        m_currentCursorImage.size    = buf->size;\n        m_currentCursorImage.pBuffer = buf;\n    }\n\n    m_currentCursorImage.hotspot = hotspot;\n    m_currentCursorImage.scale   = scale;\n\n    updateCursorBackend();\n    damageIfSoftware();\n    m_events.cursorChanged.emit();\n}\n\nvoid CPointerManager::setCursorSurface(SP<Desktop::View::CWLSurface> surf, const Vector2D& hotspot) {\n    damageIfSoftware();\n\n    if (surf == m_currentCursorImage.surface) {\n        if (hotspot != m_currentCursorImage.hotspot || (surf && surf->resource() ? surf->resource()->m_current.scale : 1.F) != m_currentCursorImage.scale) {\n            m_currentCursorImage.hotspot = hotspot;\n            m_currentCursorImage.scale   = surf && surf->resource() ? surf->resource()->m_current.scale : 1.F;\n            updateCursorBackend();\n            damageIfSoftware();\n            m_events.cursorChanged.emit();\n        }\n\n        return;\n    }\n\n    resetCursorImage(false);\n\n    if (surf) {\n        m_currentCursorImage.surface = surf;\n        m_currentCursorImage.scale   = surf->resource()->m_current.scale;\n\n        surf->resource()->map();\n\n        m_currentCursorImage.destroySurface = surf->m_events.destroy.listen([this] { resetCursorImage(); });\n        m_currentCursorImage.commitSurface  = surf->resource()->m_events.commit.listen([this] {\n            damageIfSoftware();\n            m_currentCursorImage.size  = m_currentCursorImage.surface->resource()->m_current.texture ? m_currentCursorImage.surface->resource()->m_current.bufferSize : Vector2D{};\n            m_currentCursorImage.scale = m_currentCursorImage.surface ? m_currentCursorImage.surface->resource()->m_current.scale : 1.F;\n            recheckEnteredOutputs();\n            updateCursorBackend();\n            damageIfSoftware();\n            m_events.cursorChanged.emit();\n        });\n\n        if (surf->resource()->m_current.texture) {\n            m_currentCursorImage.size = surf->resource()->m_current.bufferSize;\n            surf->resource()->frame(Time::steadyNow());\n        }\n    }\n\n    m_currentCursorImage.hotspot = hotspot;\n\n    recheckEnteredOutputs();\n    updateCursorBackend();\n    damageIfSoftware();\n    m_events.cursorChanged.emit();\n}\n\nvoid CPointerManager::recheckEnteredOutputs() {\n    if (!hasCursor())\n        return;\n\n    auto box = getCursorBoxGlobal();\n\n    for (auto const& s : m_monitorStates) {\n        if (s->monitor.expired() || s->monitor->isMirror() || !s->monitor->m_enabled)\n            continue;\n\n        const bool overlaps = box.overlaps(s->monitor->logicalBox());\n\n        if (!s->entered && overlaps) {\n            s->entered = true;\n\n            if (!m_currentCursorImage.surface)\n                continue;\n\n            m_currentCursorImage.surface->resource()->enter(s->monitor.lock());\n            PROTO::fractional->sendScale(m_currentCursorImage.surface->resource(), s->monitor->m_scale);\n            g_pCompositor->setPreferredScaleForSurface(m_currentCursorImage.surface->resource(), s->monitor->m_scale);\n        } else if (s->entered && !overlaps) {\n            s->entered = false;\n\n            // if we are using hw cursors, prevent\n            // the cursor from being stuck at the last point.\n            if (!s->hardwareFailed &&\n                (s->monitor->m_output->getBackend()->capabilities() & Aquamarine::IBackendImplementation::eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER))\n                setHWCursorBuffer(s, nullptr);\n\n            if (!m_currentCursorImage.surface)\n                continue;\n\n            m_currentCursorImage.surface->resource()->leave(s->monitor.lock());\n        }\n    }\n}\n\nvoid CPointerManager::resetCursorImage(bool apply) {\n    damageIfSoftware();\n\n    if (m_currentCursorImage.surface) {\n        for (auto const& m : g_pCompositor->m_monitors) {\n            m_currentCursorImage.surface->resource()->leave(m);\n        }\n\n        m_currentCursorImage.surface->resource()->unmap();\n\n        m_currentCursorImage.destroySurface.reset();\n        m_currentCursorImage.commitSurface.reset();\n        m_currentCursorImage.surface.reset();\n    } else if (m_currentCursorImage.pBuffer)\n        m_currentCursorImage.pBuffer = nullptr;\n\n    if (m_currentCursorImage.bufferTex)\n        m_currentCursorImage.bufferTex = nullptr;\n\n    m_currentCursorImage.scale   = 1.F;\n    m_currentCursorImage.hotspot = {0, 0};\n\n    for (auto const& s : m_monitorStates) {\n        if (s->monitor.expired() || s->monitor->isMirror() || !s->monitor->m_enabled)\n            continue;\n\n        s->entered = false;\n    }\n\n    if (!apply)\n        return;\n\n    for (auto const& ms : m_monitorStates) {\n        if (!ms->monitor || !ms->monitor->m_enabled || !ms->monitor->m_dpmsStatus) {\n            Log::logger->log(Log::TRACE, \"Not updating hw cursors: disabled / dpms off display\");\n            continue;\n        }\n\n        if (ms->cursorFrontBuffer) {\n            if (ms->monitor->m_output->getBackend()->capabilities() & Aquamarine::IBackendImplementation::eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER)\n                ms->monitor->m_output->setCursor(nullptr, {});\n            ms->cursorFrontBuffer = nullptr;\n        }\n    }\n\n    m_events.cursorChanged.emit();\n}\n\nvoid CPointerManager::updateCursorBackend() {\n    const auto CURSORBOX = getCursorBoxGlobal();\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (!m->m_enabled || !m->m_dpmsStatus) {\n            Log::logger->log(Log::TRACE, \"Not updating hw cursors: disabled / dpms off display\");\n            continue;\n        }\n\n        auto CROSSES = !m->logicalBox().intersection(CURSORBOX).empty();\n        auto state   = stateFor(m);\n\n        if (!CROSSES) {\n            if (state->cursorFrontBuffer)\n                setHWCursorBuffer(state, nullptr);\n\n            continue;\n        }\n\n        if (state->softwareLocks > 0 || g_pConfigManager->shouldUseSoftwareCursors(m) || !attemptHardwareCursor(state)) {\n            Log::logger->log(Log::TRACE, \"Output {} rejected hardware cursors, falling back to sw\", m->m_name);\n            state->box            = getCursorBoxLogicalForMonitor(state->monitor.lock());\n            state->hardwareFailed = true;\n\n            if (state->hwApplied)\n                setHWCursorBuffer(state, nullptr);\n\n            state->hwApplied = false;\n            continue;\n        }\n\n        state->hardwareFailed = false;\n    }\n}\n\nvoid CPointerManager::onCursorMoved() {\n    if (!hasCursor())\n        return;\n\n    const auto CURSORBOX = getCursorBoxGlobal();\n    bool       recalc    = false;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        auto state = stateFor(m);\n\n        state->box = getCursorBoxLogicalForMonitor(state->monitor.lock());\n\n        auto CROSSES = !m->logicalBox().intersection(CURSORBOX).empty();\n\n        if (!CROSSES && state->cursorFrontBuffer) {\n            Log::logger->log(Log::TRACE, \"onCursorMoved for output {}: cursor left the viewport, removing it from the backend\", m->m_name);\n            setHWCursorBuffer(state, nullptr);\n            continue;\n        } else if (CROSSES && !state->cursorFrontBuffer) {\n            Log::logger->log(Log::TRACE, \"onCursorMoved for output {}: cursor entered the output, but no front buffer, forcing recalc\", m->m_name);\n            recalc = true;\n        }\n\n        if (!state->entered)\n            continue;\n\n        CScopeGuard x([m] { m->onCursorMovedOnMonitor(); });\n\n        if (state->hardwareFailed)\n            continue;\n\n        const auto CURSORPOS = getCursorPosForMonitor(m);\n        m->m_output->moveCursor(CURSORPOS, m->shouldSkipScheduleFrameOnMouseEvent());\n\n        state->monitor->m_scanoutNeedsCursorUpdate = true;\n    }\n\n    if (recalc)\n        updateCursorBackend();\n}\n\nbool CPointerManager::attemptHardwareCursor(SP<CPointerManager::SMonitorPointerState> state) {\n    auto output = state->monitor->m_output;\n\n    if (!(output->getBackend()->capabilities() & Aquamarine::IBackendImplementation::eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER))\n        return false;\n\n    const auto CURSORPOS = getCursorPosForMonitor(state->monitor.lock());\n    state->monitor->m_output->moveCursor(CURSORPOS, state->monitor->shouldSkipScheduleFrameOnMouseEvent());\n\n    auto texture = getCurrentCursorTexture();\n\n    if (!texture) {\n        Log::logger->log(Log::TRACE, \"[pointer] no texture for hw cursor -> hiding\");\n        setHWCursorBuffer(state, nullptr);\n        return true;\n    }\n\n    auto buffer = renderHWCursorBuffer(state, texture);\n\n    if (!buffer) {\n        Log::logger->log(Log::TRACE, \"[pointer] hw cursor failed rendering\");\n        setHWCursorBuffer(state, nullptr);\n        return false;\n    }\n\n    bool success = setHWCursorBuffer(state, buffer);\n\n    if (!success) {\n        Log::logger->log(Log::TRACE, \"[pointer] hw cursor failed applying, hiding\");\n        setHWCursorBuffer(state, nullptr);\n        return false;\n    } else\n        state->hwApplied = true;\n\n    return success;\n}\n\nbool CPointerManager::setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquamarine::IBuffer> buf) {\n    if (!(state->monitor->m_output->getBackend()->capabilities() & Aquamarine::IBackendImplementation::eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER))\n        return false;\n\n    const auto HOTSPOT = transformedHotspot(state->monitor.lock());\n\n    Log::logger->log(Log::TRACE, \"[pointer] hw transformed hotspot for {}: {}\", state->monitor->m_name, HOTSPOT);\n\n    if (!state->monitor->m_output->setCursor(buf, HOTSPOT))\n        return false;\n\n    state->cursorFrontBuffer = buf;\n\n    if (!state->monitor->shouldSkipScheduleFrameOnMouseEvent())\n        g_pCompositor->scheduleFrameForMonitor(state->monitor.lock(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_SHAPE);\n\n    state->monitor->m_scanoutNeedsCursorUpdate = true;\n\n    return true;\n}\n\nSP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager::SMonitorPointerState> state, SP<ITexture> texture) {\n    auto        maxSize    = state->monitor->m_output->cursorPlaneSize();\n    auto const& cursorSize = m_currentCursorImage.size;\n\n    static auto PCPUBUFFER = CConfigValue<Hyprlang::INT>(\"cursor:use_cpu_buffer\");\n\n    const bool  shouldUseCpuBuffer = *PCPUBUFFER == 1 || (*PCPUBUFFER != 0 && g_pHyprRenderer->isNvidia());\n\n    if (maxSize == Vector2D{})\n        return nullptr;\n\n    if (maxSize != Vector2D{-1, -1}) {\n        if (cursorSize.x > maxSize.x || cursorSize.y > maxSize.y) {\n            Log::logger->log(Log::TRACE, \"hardware cursor too big! {} > {}\", m_currentCursorImage.size, maxSize);\n            return nullptr;\n        }\n    } else\n        maxSize = cursorSize;\n\n    if (!state->monitor->m_cursorSwapchain || maxSize != state->monitor->m_cursorSwapchain->currentOptions().size ||\n        shouldUseCpuBuffer != (state->monitor->m_cursorSwapchain->getAllocator()->type() != Aquamarine::AQ_ALLOCATOR_TYPE_GBM)) {\n\n        if (!state->monitor->m_cursorSwapchain || shouldUseCpuBuffer != (state->monitor->m_cursorSwapchain->getAllocator()->type() != Aquamarine::AQ_ALLOCATOR_TYPE_GBM)) {\n\n            auto allocator = state->monitor->m_output->getBackend()->preferredAllocator();\n            if (shouldUseCpuBuffer) {\n                for (const auto& a : state->monitor->m_output->getBackend()->getAllocators()) {\n                    if (a->type() == Aquamarine::AQ_ALLOCATOR_TYPE_DRM_DUMB) {\n                        allocator = a;\n                        break;\n                    }\n                }\n            }\n\n            auto backend                      = state->monitor->m_output->getBackend();\n            auto primary                      = backend->getPrimary();\n            state->monitor->m_cursorSwapchain = Aquamarine::CSwapchain::create(allocator, primary ? primary.lock() : backend);\n        }\n\n        auto options     = state->monitor->m_cursorSwapchain->currentOptions();\n        options.size     = maxSize;\n        options.length   = 2;\n        options.scanout  = true;\n        options.cursor   = true;\n        options.multigpu = !DRM::sameGpu(state->monitor->m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd);\n        // We do not set the format (unless shm). If it's unset (DRM_FORMAT_INVALID) then the swapchain will pick for us,\n        // but if it's set, we don't wanna change it.\n        if (shouldUseCpuBuffer)\n            options.format = DRM_FORMAT_ARGB8888;\n\n        if (!state->monitor->m_cursorSwapchain->reconfigure(options)) {\n            Log::logger->log(Log::TRACE, \"Failed to reconfigure cursor swapchain\");\n            return nullptr;\n        }\n    }\n\n    // if we already rendered the cursor, revert the swapchain to avoid rendering the cursor over\n    // the current front buffer\n    // this flag will be reset in the preRender hook, so when we commit this buffer to KMS\n    if (state->cursorRendered)\n        state->monitor->m_cursorSwapchain->rollback();\n\n    state->cursorRendered = true;\n\n    auto buf = state->monitor->m_cursorSwapchain->next(nullptr);\n    if (!buf) {\n        Log::logger->log(Log::TRACE, \"Failed to acquire a buffer from the cursor swapchain\");\n        return nullptr;\n    }\n\n    if (shouldUseCpuBuffer) {\n        // get the texture data if available.\n        auto texData = texture->dataCopy();\n        if (texData.empty()) {\n            if (m_currentCursorImage.surface && m_currentCursorImage.surface->resource()->m_role->role() == SURFACE_ROLE_CURSOR) {\n                const auto SURFACE   = m_currentCursorImage.surface->resource();\n                auto&      shmBuffer = CCursorSurfaceRole::cursorPixelData(SURFACE);\n\n                bool       flipRB = false;\n\n                if (SURFACE->m_current.texture) {\n                    Log::logger->log(Log::TRACE, \"Cursor CPU surface: format {}, expecting AR24\", NFormatUtils::drmFormatName(SURFACE->m_current.texture->m_drmFormat));\n                    if (SURFACE->m_current.texture->m_drmFormat == DRM_FORMAT_ABGR8888) {\n                        Log::logger->log(Log::TRACE, \"Cursor CPU surface format AB24, will flip. WARNING: this will break on big endian!\");\n                        flipRB = true;\n                    } else if (SURFACE->m_current.texture->m_drmFormat != DRM_FORMAT_ARGB8888) {\n                        Log::logger->log(Log::TRACE, \"Cursor CPU surface format rejected, falling back to sw\");\n                        return nullptr;\n                    }\n                }\n\n                if (shmBuffer.data())\n                    texData = shmBuffer;\n                else {\n                    texData.resize(texture->m_size.x * 4 * texture->m_size.y);\n                    memset(texData.data(), 0x00, texData.size());\n                }\n\n                if (flipRB) {\n                    for (size_t i = 0; i < shmBuffer.size(); i += 4) {\n                        std::swap(shmBuffer[i], shmBuffer[i + 2]); // little-endian!!!!!!\n                    }\n                }\n            } else {\n                Log::logger->log(Log::TRACE, \"Cannot use dumb copy on dmabuf cursor buffers\");\n                return nullptr;\n            }\n        }\n\n        // then, we just yeet it into the dumb buffer\n\n        const auto DMABUF      = buf->dmabuf();\n        auto [data, fmt, size] = buf->beginDataPtr(0);\n\n        auto CAIROSURFACE     = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, DMABUF.size.x, DMABUF.size.y);\n        auto CAIRODATASURFACE = cairo_image_surface_create_for_data(texData.data(), CAIRO_FORMAT_ARGB32, texture->m_size.x, texture->m_size.y, texture->m_size.x * 4);\n\n        auto CAIRO = cairo_create(CAIROSURFACE);\n\n        cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE);\n        cairo_set_source_rgba(CAIRO, 0, 0, 0, 0);\n        cairo_rectangle(CAIRO, 0, 0, texture->m_size.x, texture->m_size.y);\n        cairo_fill(CAIRO);\n\n        const auto PATTERNPRE = cairo_pattern_create_for_surface(CAIRODATASURFACE);\n        cairo_pattern_set_filter(PATTERNPRE, CAIRO_FILTER_BILINEAR);\n        cairo_matrix_t matrixPre;\n        cairo_matrix_init_identity(&matrixPre);\n\n        const auto TR = state->monitor->m_transform;\n\n        // we need to scale the cursor to the right size, because it might not be (esp with XCursor)\n        const auto SCALE = texture->m_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale);\n        const auto SX = SCALE.x, SY = SCALE.y;\n        const auto BW = sc<double>(DMABUF.size.x), BH = sc<double>(DMABUF.size.y);\n\n        // Cairo pattern matrix maps destination coords to source coords (inverse of visual transform).\n        // x_src = xx * x_dst + xy * y_dst + x0\n        // y_src = yx * x_dst + yy * y_dst + y0\n        // cairo_matrix_init(&m, xx, yx, xy, yy, x0, y0)\n        switch (TR) {\n            case WL_OUTPUT_TRANSFORM_NORMAL:\n            default: cairo_matrix_init(&matrixPre, SX, 0, 0, SY, 0, 0); break;\n            case WL_OUTPUT_TRANSFORM_90: cairo_matrix_init(&matrixPre, 0, SY, -SX, 0, SX * BW, 0); break;\n            case WL_OUTPUT_TRANSFORM_180: cairo_matrix_init(&matrixPre, -SX, 0, 0, -SY, SX * BW, SY * BH); break;\n            case WL_OUTPUT_TRANSFORM_270: cairo_matrix_init(&matrixPre, 0, -SY, SX, 0, 0, SY * BH); break;\n            case WL_OUTPUT_TRANSFORM_FLIPPED: cairo_matrix_init(&matrixPre, -SX, 0, 0, SY, SX * BW, 0); break;\n            case WL_OUTPUT_TRANSFORM_FLIPPED_90: cairo_matrix_init(&matrixPre, 0, SY, SX, 0, 0, 0); break;\n            case WL_OUTPUT_TRANSFORM_FLIPPED_180: cairo_matrix_init(&matrixPre, SX, 0, 0, -SY, 0, SY * BH); break;\n            case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&matrixPre, 0, -SY, -SX, 0, SX * BW, SY * BH); break;\n        }\n\n        cairo_pattern_set_matrix(PATTERNPRE, &matrixPre);\n        cairo_set_source(CAIRO, PATTERNPRE);\n        cairo_paint(CAIRO);\n\n        cairo_surface_flush(CAIROSURFACE);\n\n        cairo_pattern_destroy(PATTERNPRE);\n\n        memcpy(data, cairo_image_surface_get_data(CAIROSURFACE), sc<size_t>(cairo_image_surface_get_height(CAIROSURFACE)) * cairo_image_surface_get_stride(CAIROSURFACE));\n\n        cairo_destroy(CAIRO);\n        cairo_surface_destroy(CAIROSURFACE);\n        cairo_surface_destroy(CAIRODATASURFACE);\n\n        buf->endDataPtr();\n\n        return buf;\n    }\n\n    g_pHyprRenderer->m_renderData.pMonitor = state->monitor;\n\n    auto RBO = g_pHyprRenderer->getOrCreateRenderbuffer(buf, state->monitor->m_cursorSwapchain->currentOptions().format);\n    if (!RBO) {\n        Log::logger->log(Log::TRACE, \"Failed to create cursor RB with format {}, mod {}\", buf->dmabuf().format, buf->dmabuf().modifier);\n        return nullptr;\n    }\n\n    RBO->bind();\n\n    CRegion damageRegion = {0, 0, INT_MAX, INT_MAX};\n    g_pHyprRenderer->beginFullFakeRender(state->monitor.lock(), damageRegion, RBO->getFB());\n    g_pHyprRenderer->startRenderPass();\n    g_pHyprRenderer->draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0.F, 0.F, 0.F, 0.F}}), {});\n\n    CBox xbox = {{}, Vector2D{m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale}.round()};\n    Log::logger->log(Log::TRACE, \"[pointer] monitor: {}, size: {}, hw buf: {}, scale: {:.2f}, monscale: {:.2f}, xbox: {}\", state->monitor->m_name, m_currentCursorImage.size,\n                     cursorSize, m_currentCursorImage.scale, state->monitor->m_scale, xbox.size());\n\n    CTexPassElement::SRenderData data;\n    data.tex = texture;\n    data.box = xbox;\n    g_pHyprRenderer->draw(makeUnique<CTexPassElement>(std::move(data)), damageRegion);\n\n    g_pHyprRenderer->endRender();\n    g_pHyprRenderer->m_renderData.pMonitor.reset();\n\n    return buf;\n}\n\nvoid CPointerManager::renderSoftwareCursorsFor(PHLMONITOR pMonitor, const Time::steady_tp& now, CRegion& damage, std::optional<Vector2D> overridePos, bool forceRender) {\n    if (!hasCursor())\n        return;\n\n    auto state = stateFor(pMonitor);\n\n    if (!state->hardwareFailed && state->softwareLocks == 0 && !forceRender) {\n        if (m_currentCursorImage.surface)\n            m_currentCursorImage.surface->resource()->frame(now);\n        return;\n    }\n\n    // don't render cursor if forced but we are already using sw cursors for the monitor\n    // otherwise we draw the cursor again for screencopy when using sw cursors\n    if (forceRender && (state->hardwareFailed || state->softwareLocks != 0))\n        return;\n\n    auto box = state->box.copy();\n    if (overridePos.has_value()) {\n        box.x = overridePos->x;\n        box.y = overridePos->y;\n\n        box.translate(-m_currentCursorImage.hotspot);\n    }\n\n    if (box.intersection(CBox{{}, {pMonitor->m_size}}).empty())\n        return;\n\n    auto texture = getCurrentCursorTexture();\n    if (!texture)\n        return;\n\n    box.scale(pMonitor->m_scale);\n    box.x = std::round(box.x);\n    box.y = std::round(box.y);\n\n    CTexPassElement::SRenderData data;\n    data.tex = texture;\n    data.box = box.round();\n\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n\n    if (m_currentCursorImage.surface)\n        m_currentCursorImage.surface->resource()->frame(now);\n}\n\nVector2D CPointerManager::getCursorPosForMonitor(PHLMONITOR pMonitor) {\n    return CBox{m_pointerPos - pMonitor->m_position, {0, 0}}\n               .transform(Math::wlTransformToHyprutils(Math::invertTransform(pMonitor->m_transform)), pMonitor->m_transformedSize.x / pMonitor->m_scale,\n                          pMonitor->m_transformedSize.y / pMonitor->m_scale)\n               .pos() *\n        pMonitor->m_scale;\n}\n\nVector2D CPointerManager::transformedHotspot(PHLMONITOR pMonitor) {\n    if (!pMonitor->m_cursorSwapchain)\n        return {}; // doesn't matter, we have no hw cursor, and this is only for hw cursors\n\n    return CBox{m_currentCursorImage.hotspot * pMonitor->m_scale, {0, 0}}\n        .transform(Math::wlTransformToHyprutils(Math::invertTransform(pMonitor->m_transform)), pMonitor->m_cursorSwapchain->currentOptions().size.x,\n                   pMonitor->m_cursorSwapchain->currentOptions().size.y)\n        .pos();\n}\n\nCBox CPointerManager::getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor) {\n    return getCursorBoxGlobal().translate(-pMonitor->m_position);\n}\n\nCBox CPointerManager::getCursorBoxGlobal() {\n    return CBox{m_pointerPos, m_currentCursorImage.size / m_currentCursorImage.scale}.translate(-m_currentCursorImage.hotspot);\n}\n\nVector2D CPointerManager::closestValid(const Vector2D& pos) {\n    static auto PADDING = CConfigValue<Hyprlang::INT>(\"cursor:hotspot_padding\");\n\n    auto        CURSOR_PADDING = std::clamp(sc<int>(*PADDING), 0, 100);\n    CBox        hotBox         = {{pos.x - CURSOR_PADDING, pos.y - CURSOR_PADDING}, {2 * CURSOR_PADDING, 2 * CURSOR_PADDING}};\n\n    //\n    static auto INSIDE_LAYOUT = [this](const CBox& box) -> bool {\n        for (auto const& b : m_currentMonitorLayout.monitorBoxes) {\n            if (box.inside(b))\n                return true;\n        }\n        return false;\n    };\n\n    static auto INSIDE_LAYOUT_COORD = [this](const Vector2D& vec) -> bool {\n        for (auto const& b : m_currentMonitorLayout.monitorBoxes) {\n            if (b.containsPoint(vec))\n                return true;\n        }\n        return false;\n    };\n\n    static auto NEAREST_LAYOUT = [this](const Vector2D& vec) -> Vector2D {\n        Vector2D leader;\n        float    distanceSq = __FLT_MAX__;\n\n        for (auto const& b : m_currentMonitorLayout.monitorBoxes) {\n            auto p      = b.closestPoint(vec);\n            auto distSq = p.distanceSq(vec);\n\n            if (distSq < distanceSq) {\n                leader     = p;\n                distanceSq = distSq;\n            }\n        }\n\n        if (distanceSq > 1337.69420e+20F)\n            return {0, 0}; // ???\n\n        return leader;\n    };\n\n    if (INSIDE_LAYOUT(hotBox))\n        return pos;\n\n    Vector2D leader = NEAREST_LAYOUT(pos);\n\n    hotBox.x = leader.x - CURSOR_PADDING;\n    hotBox.y = leader.y - CURSOR_PADDING;\n\n    // push the hotbox around so that it fits in the layout\n\n    if (!INSIDE_LAYOUT_COORD(hotBox.middle() + Vector2D{CURSOR_PADDING, CURSOR_PADDING})) {\n        auto delta = NEAREST_LAYOUT(hotBox.middle() + Vector2D{CURSOR_PADDING, CURSOR_PADDING}) - (hotBox.middle() + Vector2D{CURSOR_PADDING, CURSOR_PADDING});\n        hotBox.translate(delta);\n    }\n\n    if (!INSIDE_LAYOUT_COORD(hotBox.middle() - Vector2D{CURSOR_PADDING, CURSOR_PADDING})) {\n        auto delta = NEAREST_LAYOUT(hotBox.middle() - Vector2D{CURSOR_PADDING, CURSOR_PADDING}) - (hotBox.middle() - Vector2D{CURSOR_PADDING, CURSOR_PADDING});\n        hotBox.translate(delta);\n    }\n\n    if (!INSIDE_LAYOUT_COORD(hotBox.middle() + Vector2D{CURSOR_PADDING, -CURSOR_PADDING})) {\n        auto delta = NEAREST_LAYOUT(hotBox.middle() + Vector2D{CURSOR_PADDING, -CURSOR_PADDING}) - (hotBox.middle() + Vector2D{CURSOR_PADDING, -CURSOR_PADDING});\n        hotBox.translate(delta);\n    }\n\n    if (!INSIDE_LAYOUT_COORD(hotBox.middle() + Vector2D{-CURSOR_PADDING, CURSOR_PADDING})) {\n        auto delta = NEAREST_LAYOUT(hotBox.middle() + Vector2D{-CURSOR_PADDING, CURSOR_PADDING}) - (hotBox.middle() + Vector2D{-CURSOR_PADDING, CURSOR_PADDING});\n        hotBox.translate(delta);\n    }\n\n    return hotBox.middle();\n}\n\nvoid CPointerManager::damageIfSoftware() {\n    if (g_pCompositor->m_unsafeState)\n        return;\n\n    auto b = getCursorBoxGlobal().expand(4);\n\n    for (auto const& mw : m_monitorStates) {\n        auto monitor = mw->monitor.lock();\n        if (!monitor || !monitor->m_output || monitor->isMirror())\n            continue;\n\n        auto usesSoftwareCursor = (mw->softwareLocks > 0 || mw->hardwareFailed || g_pConfigManager->shouldUseSoftwareCursors(monitor));\n        if (!usesSoftwareCursor)\n            continue;\n\n        auto shouldAddDamage = !monitor->shouldSkipScheduleFrameOnMouseEvent() && b.overlaps({monitor->m_position, monitor->m_size});\n        if (!shouldAddDamage)\n            continue;\n\n        CBox damageBox = b.copy().translate(-monitor->m_position).scale(monitor->m_scale).round();\n        monitor->addDamage(damageBox);\n    }\n}\n\nvoid CPointerManager::warpTo(const Vector2D& logical) {\n    damageIfSoftware();\n\n    m_pointerPos = closestValid(logical);\n\n    if (!g_pInputManager->isLocked()) {\n        recheckEnteredOutputs();\n        onCursorMoved();\n    }\n\n    damageIfSoftware();\n}\n\nvoid CPointerManager::move(const Vector2D& deltaLogical) {\n    const auto oldPos = m_pointerPos;\n    auto       newPos = oldPos + Vector2D{std::isnan(deltaLogical.x) ? 0.0 : deltaLogical.x, std::isnan(deltaLogical.y) ? 0.0 : deltaLogical.y};\n\n    warpTo(newPos);\n}\n\nvoid CPointerManager::warpAbsolute(Vector2D abs, SP<IHID> dev) {\n    if (!dev)\n        return;\n\n    if (!std::isnan(abs.x))\n        abs.x = std::clamp(abs.x, 0.0, 1.0);\n    if (!std::isnan(abs.y))\n        abs.y = std::clamp(abs.y, 0.0, 1.0);\n\n    // find x and y size of the entire space\n    const auto& MONITORS = g_pCompositor->m_monitors;\n    Vector2D    topLeft = MONITORS.at(0)->m_position, bottomRight = MONITORS.at(0)->m_position + MONITORS.at(0)->m_size;\n    for (size_t i = 1; i < MONITORS.size(); ++i) {\n        const auto EXTENT = MONITORS[i]->logicalBox().extent();\n        const auto POS    = MONITORS[i]->logicalBox().pos();\n        if (EXTENT.x > bottomRight.x)\n            bottomRight.x = EXTENT.x;\n        if (EXTENT.y > bottomRight.y)\n            bottomRight.y = EXTENT.y;\n        if (POS.x < topLeft.x)\n            topLeft.x = POS.x;\n        if (POS.y < topLeft.y)\n            topLeft.y = POS.y;\n    }\n    CBox mappedArea = {topLeft, bottomRight - topLeft};\n\n    auto outputMappedArea = [&mappedArea](const std::string& output) {\n        if (output == \"current\") {\n            if (const auto PLASTMONITOR = Desktop::focusState()->monitor(); PLASTMONITOR)\n                return PLASTMONITOR->logicalBox();\n        } else if (const auto PMONITOR = g_pCompositor->getMonitorFromString(output); PMONITOR)\n            return PMONITOR->logicalBox();\n        return mappedArea;\n    };\n\n    switch (dev->getType()) {\n        case HID_TYPE_TABLET: {\n            CTablet* TAB = rc<CTablet*>(dev.get());\n            if (!TAB->m_boundOutput.empty()) {\n                mappedArea = outputMappedArea(TAB->m_boundOutput);\n                mappedArea.translate(TAB->m_boundBox.pos());\n            } else if (TAB->m_absolutePos) {\n                mappedArea.x = TAB->m_boundBox.x;\n                mappedArea.y = TAB->m_boundBox.y;\n            } else\n                mappedArea.translate(TAB->m_boundBox.pos());\n\n            if (!TAB->m_boundBox.empty()) {\n                mappedArea.w = TAB->m_boundBox.w;\n                mappedArea.h = TAB->m_boundBox.h;\n            }\n            break;\n        }\n        case HID_TYPE_TOUCH: {\n            ITouch* TOUCH = rc<ITouch*>(dev.get());\n            if (!TOUCH->m_boundOutput.empty())\n                mappedArea = outputMappedArea(TOUCH->m_boundOutput);\n            break;\n        }\n        case HID_TYPE_POINTER: {\n            IPointer* POINTER = rc<IPointer*>(dev.get());\n            if (!POINTER->m_boundOutput.empty())\n                mappedArea = outputMappedArea(POINTER->m_boundOutput);\n            break;\n        }\n        default: break;\n    }\n\n    damageIfSoftware();\n\n    if (std::isnan(abs.x) || std::isnan(abs.y)) {\n        m_pointerPos.x = std::isnan(abs.x) ? m_pointerPos.x : mappedArea.x + mappedArea.w * abs.x;\n        m_pointerPos.y = std::isnan(abs.y) ? m_pointerPos.y : mappedArea.y + mappedArea.h * abs.y;\n    } else\n        m_pointerPos = mappedArea.pos() + mappedArea.size() * abs;\n\n    onCursorMoved();\n    recheckEnteredOutputs();\n\n    damageIfSoftware();\n}\n\nvoid CPointerManager::onMonitorLayoutChange() {\n    m_currentMonitorLayout.monitorBoxes.clear();\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m->isMirror() || !m->m_enabled || !m->m_output)\n            continue;\n\n        m_currentMonitorLayout.monitorBoxes.emplace_back(m->m_position, m->m_size);\n    }\n\n    damageIfSoftware();\n\n    m_pointerPos = closestValid(m_pointerPos);\n    updateCursorBackend();\n    recheckEnteredOutputs();\n\n    damageIfSoftware();\n}\n\nconst CPointerManager::SCursorImage& CPointerManager::currentCursorImage() {\n    return m_currentCursorImage;\n}\n\nSP<ITexture> CPointerManager::getCurrentCursorTexture() {\n    if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture))\n        return nullptr;\n\n    if (m_currentCursorImage.pBuffer) {\n        if (!m_currentCursorImage.bufferTex)\n            m_currentCursorImage.bufferTex = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true);\n        return m_currentCursorImage.bufferTex;\n    }\n\n    return m_currentCursorImage.surface->resource()->m_current.texture;\n}\n\nvoid CPointerManager::attachPointer(SP<IPointer> pointer) {\n    if (!pointer)\n        return;\n\n    static auto PMOUSEDPMS = CConfigValue<Hyprlang::INT>(\"misc:mouse_move_enables_dpms\");\n\n    //\n    auto listener = m_pointerListeners.emplace_back(makeShared<SPointerListener>());\n\n    listener->pointer = pointer;\n\n    listener->destroy = pointer->m_events.destroy.listen([this] { detachPointer(nullptr); });\n    listener->motion  = pointer->m_pointerEvents.motion.listen([](const IPointer::SMotionEvent& event) {\n        g_pInputManager->onMouseMoved(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->motionAbsolute = pointer->m_pointerEvents.motionAbsolute.listen([](const IPointer::SMotionAbsoluteEvent& event) {\n        g_pInputManager->onMouseWarp(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->button = pointer->m_pointerEvents.button.listen([weak = WP<IPointer>(pointer)](const IPointer::SButtonEvent& event) {\n        g_pInputManager->onMouseButton(event, weak.lock());\n        PROTO::idle->onActivity();\n    });\n\n    listener->axis  = pointer->m_pointerEvents.axis.listen([weak = WP<IPointer>(pointer)](const IPointer::SAxisEvent& event) {\n        g_pInputManager->onMouseWheel(event, weak.lock());\n        PROTO::idle->onActivity();\n    });\n    listener->frame = pointer->m_pointerEvents.frame.listen([] { g_pInputManager->onPointerFrame(); });\n\n    listener->swipeBegin = pointer->m_pointerEvents.swipeBegin.listen([](const IPointer::SSwipeBeginEvent& event) {\n        g_pInputManager->onSwipeBegin(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->swipeEnd = pointer->m_pointerEvents.swipeEnd.listen([](const IPointer::SSwipeEndEvent& event) {\n        g_pInputManager->onSwipeEnd(event);\n        PROTO::idle->onActivity();\n    });\n\n    listener->swipeUpdate = pointer->m_pointerEvents.swipeUpdate.listen([](const IPointer::SSwipeUpdateEvent& event) {\n        g_pInputManager->onSwipeUpdate(event);\n        PROTO::idle->onActivity();\n    });\n\n    listener->pinchBegin = pointer->m_pointerEvents.pinchBegin.listen([](const IPointer::SPinchBeginEvent& event) {\n        g_pInputManager->onPinchBegin(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->pinchEnd = pointer->m_pointerEvents.pinchEnd.listen([](const IPointer::SPinchEndEvent& event) {\n        g_pInputManager->onPinchEnd(event);\n\n        PROTO::idle->onActivity();\n    });\n\n    listener->pinchUpdate = pointer->m_pointerEvents.pinchUpdate.listen([](const IPointer::SPinchUpdateEvent& event) {\n        g_pInputManager->onPinchUpdate(event);\n\n        PROTO::idle->onActivity();\n    });\n\n    listener->holdBegin = pointer->m_pointerEvents.holdBegin.listen([](const IPointer::SHoldBeginEvent& event) {\n        PROTO::pointerGestures->holdBegin(event.timeMs, event.fingers);\n        PROTO::idle->onActivity();\n    });\n\n    listener->holdEnd = pointer->m_pointerEvents.holdEnd.listen([](const IPointer::SHoldEndEvent& event) {\n        PROTO::pointerGestures->holdEnd(event.timeMs, event.cancelled);\n        PROTO::idle->onActivity();\n    });\n\n    Log::logger->log(Log::DEBUG, \"Attached pointer {} to global\", pointer->m_hlName);\n}\n\nvoid CPointerManager::attachTouch(SP<ITouch> touch) {\n    if (!touch)\n        return;\n\n    static auto PMOUSEDPMS = CConfigValue<Hyprlang::INT>(\"misc:mouse_move_enables_dpms\");\n\n    //\n    auto listener = m_touchListeners.emplace_back(makeShared<STouchListener>());\n\n    listener->touch = touch;\n\n    listener->destroy = touch->m_events.destroy.listen([this] { detachTouch(nullptr); });\n\n    listener->down = touch->m_touchEvents.down.listen([](const ITouch::SDownEvent& event) {\n        g_pInputManager->onTouchDown(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->up = touch->m_touchEvents.up.listen([](const ITouch::SUpEvent& event) {\n        g_pInputManager->onTouchUp(event);\n        PROTO::idle->onActivity();\n    });\n\n    listener->motion = touch->m_touchEvents.motion.listen([](const ITouch::SMotionEvent& event) {\n        g_pInputManager->onTouchMove(event);\n        PROTO::idle->onActivity();\n    });\n\n    listener->cancel = touch->m_touchEvents.cancel.listen([] {\n        //\n    });\n\n    listener->frame = touch->m_touchEvents.frame.listen([] { g_pSeatManager->sendTouchFrame(); });\n\n    Log::logger->log(Log::DEBUG, \"Attached touch {} to global\", touch->m_hlName);\n}\n\nvoid CPointerManager::attachTablet(SP<CTablet> tablet) {\n    if (!tablet)\n        return;\n\n    static auto PMOUSEDPMS = CConfigValue<Hyprlang::INT>(\"misc:mouse_move_enables_dpms\");\n\n    //\n    auto listener = m_tabletListeners.emplace_back(makeShared<STabletListener>());\n\n    listener->tablet = tablet;\n\n    listener->destroy = tablet->m_events.destroy.listen([this] { detachTablet(nullptr); });\n\n    listener->axis = tablet->m_tabletEvents.axis.listen([](const CTablet::SAxisEvent& event) {\n        g_pInputManager->onTabletAxis(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->proximity = tablet->m_tabletEvents.proximity.listen([](const CTablet::SProximityEvent& event) {\n        g_pInputManager->onTabletProximity(event);\n        PROTO::idle->onActivity();\n    });\n\n    listener->tip = tablet->m_tabletEvents.tip.listen([](const CTablet::STipEvent& event) {\n        g_pInputManager->onTabletTip(event);\n\n        PROTO::idle->onActivity();\n\n        if (!g_pCompositor->m_dpmsStateOn && *PMOUSEDPMS)\n            CKeybindManager::dpms(\"on\");\n    });\n\n    listener->button = tablet->m_tabletEvents.button.listen([](const CTablet::SButtonEvent& event) {\n        g_pInputManager->onTabletButton(event);\n        PROTO::idle->onActivity();\n    });\n    // clang-format on\n\n    Log::logger->log(Log::DEBUG, \"Attached tablet {} to global\", tablet->m_hlName);\n}\n\nvoid CPointerManager::detachPointer(SP<IPointer> pointer) {\n    std::erase_if(m_pointerListeners, [pointer](const auto& e) { return e->pointer.expired() || e->pointer == pointer; });\n}\n\nvoid CPointerManager::detachTouch(SP<ITouch> touch) {\n    std::erase_if(m_touchListeners, [touch](const auto& e) { return e->touch.expired() || e->touch == touch; });\n}\n\nvoid CPointerManager::detachTablet(SP<CTablet> tablet) {\n    std::erase_if(m_tabletListeners, [tablet](const auto& e) { return e->tablet.expired() || e->tablet == tablet; });\n}\n\nvoid CPointerManager::damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule) {\n    for (auto const& mw : m_monitorStates) {\n        if (mw->monitor != pMonitor)\n            continue;\n\n        auto b = getCursorBoxGlobal().intersection(pMonitor->logicalBox());\n\n        if (b.empty())\n            return;\n\n        g_pHyprRenderer->damageBox(b, skipFrameSchedule);\n\n        return;\n    }\n}\n\nVector2D CPointerManager::cursorSizeLogical() {\n    return m_currentCursorImage.size / m_currentCursorImage.scale;\n}\n"
  },
  {
    "path": "src/managers/PointerManager.hpp",
    "content": "#pragma once\n\n#include \"../devices/IPointer.hpp\"\n#include \"../devices/ITouch.hpp\"\n#include \"../devices/Tablet.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../helpers/sync/SyncTimeline.hpp\"\n#include \"../helpers/time/Time.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include <tuple>\n\nclass CMonitor;\nclass IHID;\nclass ITexture;\n\nAQUAMARINE_FORWARD(IBuffer);\n\n/*\n    The naming here is a bit confusing.\n    CPointerManager manages the _position_ and _displaying_ of the cursor,\n    but the CCursorManager _only_ manages the actual image (texture) and size\n    of the cursor.\n*/\n\nclass CPointerManager {\n  public:\n    CPointerManager();\n\n    void attachPointer(SP<IPointer> pointer);\n    void attachTouch(SP<ITouch> touch);\n    void attachTablet(SP<CTablet> tablet);\n\n    void detachPointer(SP<IPointer> pointer);\n    void detachTouch(SP<ITouch> touch);\n    void detachTablet(SP<CTablet> tablet);\n\n    // only clamps to the layout.\n    void warpTo(const Vector2D& logical);\n    void move(const Vector2D& deltaLogical);\n    void warpAbsolute(Vector2D abs, SP<IHID> dev);\n\n    void setCursorBuffer(SP<Aquamarine::IBuffer> buf, const Vector2D& hotspot, const float& scale);\n    void setCursorSurface(SP<Desktop::View::CWLSurface> buf, const Vector2D& hotspot);\n    void resetCursorImage(bool apply = true);\n\n    void lockSoftwareForMonitor(PHLMONITOR pMonitor);\n    void unlockSoftwareForMonitor(PHLMONITOR pMonitor);\n    void lockSoftwareAll();\n    void unlockSoftwareAll();\n    bool softwareLockedFor(PHLMONITOR pMonitor);\n    bool hasVisibleHWCursor(PHLMONITOR pMonitor);\n\n    void renderSoftwareCursorsFor(PHLMONITOR pMonitor, const Time::steady_tp& now, CRegion& damage /* logical */, std::optional<Vector2D> overridePos = {} /* monitor-local */,\n                                  bool forceRender = false);\n\n    // this is needed e.g. during screensharing where\n    // the software cursors aren't locked during the cursor move, but they\n    // are rendered later.\n    void damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule = false);\n\n    //\n    Vector2D position();\n    Vector2D hotspot();\n    Vector2D cursorSizeLogical();\n\n    void     recheckEnteredOutputs();\n\n    // returns the thing in global coords\n    CBox getCursorBoxGlobal();\n\n    struct SCursorImage {\n        SP<Aquamarine::IBuffer>       pBuffer;\n        SP<ITexture>                  bufferTex;\n        WP<Desktop::View::CWLSurface> surface;\n\n        Vector2D                      hotspot;\n        Vector2D                      size;\n        float                         scale = 1.F;\n\n        CHyprSignalListener           destroySurface;\n        CHyprSignalListener           commitSurface;\n    };\n\n    const SCursorImage& currentCursorImage();\n    SP<ITexture>        getCurrentCursorTexture();\n\n    struct {\n        CSignalT<> cursorChanged;\n    } m_events;\n\n  private:\n    void recheckPointerPosition();\n    void onMonitorLayoutChange();\n    void onMonitorDisconnect();\n    void updateCursorBackend();\n    void onCursorMoved();\n    bool hasCursor();\n    void damageIfSoftware();\n\n    // closest valid point to a given one\n    Vector2D closestValid(const Vector2D& pos);\n\n    // returns the thing in device coordinates. Is NOT offset by the hotspot, relies on set_cursor with hotspot.\n    Vector2D getCursorPosForMonitor(PHLMONITOR pMonitor);\n    // returns the thing in logical coordinates of the monitor\n    CBox     getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor);\n\n    Vector2D transformedHotspot(PHLMONITOR pMonitor);\n\n    struct SPointerListener {\n        CHyprSignalListener destroy;\n        CHyprSignalListener motion;\n        CHyprSignalListener motionAbsolute;\n        CHyprSignalListener button;\n        CHyprSignalListener axis;\n        CHyprSignalListener frame;\n\n        CHyprSignalListener swipeBegin;\n        CHyprSignalListener swipeEnd;\n        CHyprSignalListener swipeUpdate;\n\n        CHyprSignalListener pinchBegin;\n        CHyprSignalListener pinchEnd;\n        CHyprSignalListener pinchUpdate;\n\n        CHyprSignalListener holdBegin;\n        CHyprSignalListener holdEnd;\n\n        WP<IPointer>        pointer;\n    };\n    std::vector<SP<SPointerListener>> m_pointerListeners;\n\n    struct STouchListener {\n        CHyprSignalListener destroy;\n        CHyprSignalListener down;\n        CHyprSignalListener up;\n        CHyprSignalListener motion;\n        CHyprSignalListener cancel;\n        CHyprSignalListener frame;\n\n        WP<ITouch>          touch;\n    };\n    std::vector<SP<STouchListener>> m_touchListeners;\n\n    struct STabletListener {\n        CHyprSignalListener destroy;\n        CHyprSignalListener axis;\n        CHyprSignalListener proximity;\n        CHyprSignalListener tip;\n        CHyprSignalListener button;\n\n        WP<CTablet>         tablet;\n    };\n    std::vector<SP<STabletListener>> m_tabletListeners;\n\n    struct {\n        std::vector<CBox> monitorBoxes;\n    } m_currentMonitorLayout;\n\n    SCursorImage m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors\n\n    Vector2D     m_pointerPos = {0, 0};\n\n    struct SMonitorPointerState {\n        SMonitorPointerState(const PHLMONITOR& m) : monitor(m) {}\n        ~SMonitorPointerState() = default;\n\n        PHLMONITORREF           monitor;\n\n        int                     softwareLocks  = 0;\n        bool                    hardwareFailed = false;\n        CBox                    box; // logical\n        bool                    entered        = false;\n        bool                    hwApplied      = false;\n        bool                    cursorRendered = false;\n\n        SP<Aquamarine::IBuffer> cursorFrontBuffer;\n    };\n\n    std::vector<SP<SMonitorPointerState>> m_monitorStates;\n    SP<SMonitorPointerState>              stateFor(PHLMONITOR mon);\n    bool                                  attemptHardwareCursor(SP<SMonitorPointerState> state);\n    SP<Aquamarine::IBuffer>               renderHWCursorBuffer(SP<SMonitorPointerState> state, SP<ITexture> texture);\n    bool                                  setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquamarine::IBuffer> buf);\n\n    struct {\n        CHyprSignalListener monitorAdded;\n        CHyprSignalListener monitorPreRender;\n    } m_hooks;\n};\n\ninline UP<CPointerManager> g_pPointerManager;\n"
  },
  {
    "path": "src/managers/ProtocolManager.cpp",
    "content": "#include \"ProtocolManager.hpp\"\n\n#include \"../config/ConfigValue.hpp\"\n\n#include \"../protocols/TearingControl.hpp\"\n#include \"../protocols/FractionalScale.hpp\"\n#include \"../protocols/XDGOutput.hpp\"\n#include \"../protocols/CursorShape.hpp\"\n#include \"../protocols/IdleInhibit.hpp\"\n#include \"../protocols/RelativePointer.hpp\"\n#include \"../protocols/XDGDecoration.hpp\"\n#include \"../protocols/AlphaModifier.hpp\"\n#include \"../protocols/GammaControl.hpp\"\n#include \"../protocols/ForeignToplevel.hpp\"\n#include \"../protocols/PointerGestures.hpp\"\n#include \"../protocols/ForeignToplevelWlr.hpp\"\n#include \"../protocols/ShortcutsInhibit.hpp\"\n#include \"../protocols/TextInputV3.hpp\"\n#include \"../protocols/PointerConstraints.hpp\"\n#include \"../protocols/OutputPower.hpp\"\n#include \"../protocols/XDGActivation.hpp\"\n#include \"../protocols/IdleNotify.hpp\"\n#include \"../protocols/LockNotify.hpp\"\n#include \"../protocols/SessionLock.hpp\"\n#include \"../protocols/InputMethodV2.hpp\"\n#include \"../protocols/VirtualKeyboard.hpp\"\n#include \"../protocols/VirtualPointer.hpp\"\n#include \"../protocols/OutputManagement.hpp\"\n#include \"../protocols/ServerDecorationKDE.hpp\"\n#include \"../protocols/FocusGrab.hpp\"\n#include \"../protocols/Tablet.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../protocols/PresentationTime.hpp\"\n#include \"../protocols/XDGShell.hpp\"\n#include \"../protocols/DataDeviceWlr.hpp\"\n#include \"../protocols/PrimarySelection.hpp\"\n#include \"../protocols/XWaylandShell.hpp\"\n#include \"../protocols/Viewporter.hpp\"\n#include \"../protocols/MesaDRM.hpp\"\n#include \"../protocols/LinuxDMABUF.hpp\"\n#include \"../protocols/DRMLease.hpp\"\n#include \"../protocols/DRMSyncobj.hpp\"\n#include \"../protocols/Screencopy.hpp\"\n#include \"../protocols/ToplevelExport.hpp\"\n#include \"../protocols/ToplevelMapping.hpp\"\n#include \"../protocols/TextInputV1.hpp\"\n#include \"../protocols/GlobalShortcuts.hpp\"\n#include \"../protocols/XDGDialog.hpp\"\n#include \"../protocols/SinglePixel.hpp\"\n#include \"../protocols/SecurityContext.hpp\"\n#include \"../protocols/CTMControl.hpp\"\n#include \"../protocols/HyprlandSurface.hpp\"\n#include \"../protocols/ImageCaptureSource.hpp\"\n#include \"../protocols/ImageCopyCapture.hpp\"\n#include \"../protocols/core/Seat.hpp\"\n#include \"../protocols/core/DataDevice.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../protocols/core/Subcompositor.hpp\"\n#include \"../protocols/core/Output.hpp\"\n#include \"../protocols/core/Shm.hpp\"\n#include \"../protocols/ColorManagement.hpp\"\n#include \"../protocols/ContentType.hpp\"\n#include \"../protocols/XDGTag.hpp\"\n#include \"../protocols/XDGBell.hpp\"\n#include \"../protocols/ExtWorkspace.hpp\"\n#include \"../protocols/ExtDataDevice.hpp\"\n#include \"../protocols/PointerWarp.hpp\"\n#include \"../protocols/Fifo.hpp\"\n#include \"../protocols/CommitTiming.hpp\"\n\n#include \"../helpers/Monitor.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../Compositor.hpp\"\n#include \"content-type-v1.hpp\"\n\n#include <aquamarine/buffer/Buffer.hpp>\n#include <xf86drm.h>\n#include <aquamarine/backend/Backend.hpp>\n#include <hyprutils/memory/UniquePtr.hpp>\n\n// ********************************************************************************************\n// * IMPORTANT: make sure to .reset() any protocol UP's you create! (put reset in destructor) *\n// * otherwise Hyprland might crash when exiting.                                             *\n// ********************************************************************************************\n\nvoid CProtocolManager::onMonitorModeChange(PHLMONITOR pMonitor) {\n    const bool ISMIRROR = pMonitor->isMirror();\n\n    // onModeChanged we check if the current mirror status matches the global.\n    // mirrored outputs should have their global removed, as they are not physical parts of the\n    // layout.\n\n    if (ISMIRROR && PROTO::outputs.contains(pMonitor->m_name))\n        PROTO::outputs.at(pMonitor->m_name)->remove();\n    else if (!ISMIRROR && (!PROTO::outputs.contains(pMonitor->m_name) || PROTO::outputs.at(pMonitor->m_name)->isDefunct())) {\n        if (PROTO::outputs.contains(pMonitor->m_name))\n            PROTO::outputs.erase(pMonitor->m_name);\n        PROTO::outputs.emplace(pMonitor->m_name, makeShared<CWLOutputProtocol>(&wl_output_interface, 4, std::format(\"WLOutput ({})\", pMonitor->m_name), pMonitor->m_self.lock()));\n    }\n\n    if (PROTO::colorManagement && g_pCompositor->shouldChangePreferredImageDescription()) {\n        Log::logger->log(Log::ERR, \"FIXME: color management protocol is enabled, need a preferred image description id\");\n        PROTO::colorManagement->onImagePreferredChanged(0);\n    }\n}\n\nCProtocolManager::CProtocolManager() {\n\n    static const auto PENABLECM = CConfigValue<Hyprlang::INT>(\"render:cm_enabled\");\n    static const auto PDEBUGCM  = CConfigValue<Hyprlang::INT>(\"debug:full_cm_proto\");\n\n    static const auto PENABLECT = CConfigValue<Hyprlang::INT>(\"render:commit_timing_enabled\");\n\n    // Outputs are a bit dumb, we have to agree.\n    static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) {\n        // ignore mirrored outputs. I don't think this will ever be hit as mirrors are applied after\n        // this event is emitted iirc.\n        // also ignore the fallback\n        if (M->isMirror() || M == g_pCompositor->m_unsafeOutput)\n            return;\n\n        if (PROTO::outputs.contains(M->m_name))\n            PROTO::outputs.erase(M->m_name);\n\n        auto ref = makeShared<CWLOutputProtocol>(&wl_output_interface, 4, std::format(\"WLOutput ({})\", M->m_name), M->m_self.lock());\n        PROTO::outputs.emplace(M->m_name, ref);\n        ref->m_self = ref;\n\n        m_modeChangeListeners[M->m_name] = M->m_events.modeChanged.listen([this, M] { onMonitorModeChange(M); });\n    });\n\n    static auto P2 = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR M) {\n        if (!PROTO::outputs.contains(M->m_name))\n            return;\n        PROTO::outputs.at(M->m_name)->remove();\n        m_modeChangeListeners.erase(M->m_name);\n    });\n\n    // Core\n    PROTO::seat          = makeUnique<CWLSeatProtocol>(&wl_seat_interface, 9, \"WLSeat\");\n    PROTO::data          = makeUnique<CWLDataDeviceProtocol>(&wl_data_device_manager_interface, 3, \"WLDataDevice\");\n    PROTO::compositor    = makeUnique<CWLCompositorProtocol>(&wl_compositor_interface, 6, \"WLCompositor\");\n    PROTO::subcompositor = makeUnique<CWLSubcompositorProtocol>(&wl_subcompositor_interface, 1, \"WLSubcompositor\");\n    PROTO::shm           = makeUnique<CWLSHMProtocol>(&wl_shm_interface, 2, \"WLSHM\");\n\n    // Extensions\n    PROTO::viewport            = makeUnique<CViewporterProtocol>(&wp_viewporter_interface, 1, \"Viewporter\");\n    PROTO::tearing             = makeUnique<CTearingControlProtocol>(&wp_tearing_control_manager_v1_interface, 1, \"TearingControl\");\n    PROTO::fractional          = makeUnique<CFractionalScaleProtocol>(&wp_fractional_scale_manager_v1_interface, 1, \"FractionalScale\");\n    PROTO::xdgOutput           = makeUnique<CXDGOutputProtocol>(&zxdg_output_manager_v1_interface, 3, \"XDGOutput\");\n    PROTO::cursorShape         = makeUnique<CCursorShapeProtocol>(&wp_cursor_shape_manager_v1_interface, 2, \"CursorShape\");\n    PROTO::idleInhibit         = makeUnique<CIdleInhibitProtocol>(&zwp_idle_inhibit_manager_v1_interface, 1, \"IdleInhibit\");\n    PROTO::relativePointer     = makeUnique<CRelativePointerProtocol>(&zwp_relative_pointer_manager_v1_interface, 1, \"RelativePointer\");\n    PROTO::xdgDecoration       = makeUnique<CXDGDecorationProtocol>(&zxdg_decoration_manager_v1_interface, 1, \"XDGDecoration\");\n    PROTO::alphaModifier       = makeUnique<CAlphaModifierProtocol>(&wp_alpha_modifier_v1_interface, 1, \"AlphaModifier\");\n    PROTO::gamma               = makeUnique<CGammaControlProtocol>(&zwlr_gamma_control_manager_v1_interface, 1, \"GammaControl\");\n    PROTO::foreignToplevel     = makeUnique<CForeignToplevelProtocol>(&ext_foreign_toplevel_list_v1_interface, 1, \"ForeignToplevel\");\n    PROTO::pointerGestures     = makeUnique<CPointerGesturesProtocol>(&zwp_pointer_gestures_v1_interface, 3, \"PointerGestures\");\n    PROTO::foreignToplevelWlr  = makeUnique<CForeignToplevelWlrProtocol>(&zwlr_foreign_toplevel_manager_v1_interface, 3, \"ForeignToplevelWlr\");\n    PROTO::shortcutsInhibit    = makeUnique<CKeyboardShortcutsInhibitProtocol>(&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1, \"ShortcutsInhibit\");\n    PROTO::textInputV1         = makeUnique<CTextInputV1Protocol>(&zwp_text_input_manager_v1_interface, 1, \"TextInputV1\");\n    PROTO::textInputV3         = makeUnique<CTextInputV3Protocol>(&zwp_text_input_manager_v3_interface, 1, \"TextInputV3\");\n    PROTO::constraints         = makeUnique<CPointerConstraintsProtocol>(&zwp_pointer_constraints_v1_interface, 1, \"PointerConstraints\");\n    PROTO::outputPower         = makeUnique<COutputPowerProtocol>(&zwlr_output_power_manager_v1_interface, 1, \"OutputPower\");\n    PROTO::activation          = makeUnique<CXDGActivationProtocol>(&xdg_activation_v1_interface, 1, \"XDGActivation\");\n    PROTO::idle                = makeUnique<CIdleNotifyProtocol>(&ext_idle_notifier_v1_interface, 2, \"IdleNotify\");\n    PROTO::lockNotify          = makeUnique<CLockNotifyProtocol>(&hyprland_lock_notifier_v1_interface, 1, \"IdleNotify\");\n    PROTO::sessionLock         = makeUnique<CSessionLockProtocol>(&ext_session_lock_manager_v1_interface, 1, \"SessionLock\");\n    PROTO::ime                 = makeUnique<CInputMethodV2Protocol>(&zwp_input_method_manager_v2_interface, 1, \"IMEv2\");\n    PROTO::virtualKeyboard     = makeUnique<CVirtualKeyboardProtocol>(&zwp_virtual_keyboard_manager_v1_interface, 1, \"VirtualKeyboard\");\n    PROTO::virtualPointer      = makeUnique<CVirtualPointerProtocol>(&zwlr_virtual_pointer_manager_v1_interface, 2, \"VirtualPointer\");\n    PROTO::outputManagement    = makeUnique<COutputManagementProtocol>(&zwlr_output_manager_v1_interface, 4, \"OutputManagement\");\n    PROTO::serverDecorationKDE = makeUnique<CServerDecorationKDEProtocol>(&org_kde_kwin_server_decoration_manager_interface, 1, \"ServerDecorationKDE\");\n    PROTO::focusGrab           = makeUnique<CFocusGrabProtocol>(&hyprland_focus_grab_manager_v1_interface, 1, \"FocusGrab\");\n    PROTO::tablet              = makeUnique<CTabletV2Protocol>(&zwp_tablet_manager_v2_interface, 1, \"TabletV2\");\n    PROTO::layerShell          = makeUnique<CLayerShellProtocol>(&zwlr_layer_shell_v1_interface, 5, \"LayerShell\");\n    PROTO::presentation        = makeUnique<CPresentationProtocol>(&wp_presentation_interface, 2, \"Presentation\");\n    PROTO::xdgShell            = makeUnique<CXDGShellProtocol>(&xdg_wm_base_interface, 7, \"XDGShell\");\n    PROTO::dataWlr             = makeUnique<CDataDeviceWLRProtocol>(&zwlr_data_control_manager_v1_interface, 2, \"DataDeviceWlr\");\n    PROTO::primarySelection    = makeUnique<CPrimarySelectionProtocol>(&zwp_primary_selection_device_manager_v1_interface, 1, \"PrimarySelection\");\n    PROTO::xwaylandShell       = makeUnique<CXWaylandShellProtocol>(&xwayland_shell_v1_interface, 1, \"XWaylandShell\");\n    PROTO::toplevelMapping     = makeUnique<CToplevelMappingProtocol>(&hyprland_toplevel_mapping_manager_v1_interface, 1, \"ToplevelMapping\");\n    PROTO::globalShortcuts     = makeUnique<CGlobalShortcutsProtocol>(&hyprland_global_shortcuts_manager_v1_interface, 1, \"GlobalShortcuts\");\n    PROTO::xdgDialog           = makeUnique<CXDGDialogProtocol>(&xdg_wm_dialog_v1_interface, 1, \"XDGDialog\");\n    PROTO::singlePixel         = makeUnique<CSinglePixelProtocol>(&wp_single_pixel_buffer_manager_v1_interface, 1, \"SinglePixel\");\n    PROTO::securityContext     = makeUnique<CSecurityContextProtocol>(&wp_security_context_manager_v1_interface, 1, \"SecurityContext\");\n    PROTO::ctm                 = makeUnique<CHyprlandCTMControlProtocol>(&hyprland_ctm_control_manager_v1_interface, 2, \"CTMControl\");\n    PROTO::hyprlandSurface     = makeUnique<CHyprlandSurfaceProtocol>(&hyprland_surface_manager_v1_interface, 2, \"HyprlandSurface\");\n    PROTO::contentType         = makeUnique<CContentTypeProtocol>(&wp_content_type_manager_v1_interface, 1, \"ContentType\");\n    PROTO::xdgTag              = makeUnique<CXDGToplevelTagProtocol>(&xdg_toplevel_tag_manager_v1_interface, 1, \"XDGTag\");\n    PROTO::xdgBell             = makeUnique<CXDGSystemBellProtocol>(&xdg_system_bell_v1_interface, 1, \"XDGBell\");\n    PROTO::extWorkspace        = makeUnique<CExtWorkspaceProtocol>(&ext_workspace_manager_v1_interface, 1, \"ExtWorkspace\");\n    PROTO::extDataDevice       = makeUnique<CExtDataDeviceProtocol>(&ext_data_control_manager_v1_interface, 1, \"ExtDataDevice\");\n    PROTO::pointerWarp         = makeUnique<CPointerWarpProtocol>(&wp_pointer_warp_v1_interface, 1, \"PointerWarp\");\n    PROTO::fifo                = makeUnique<CFifoProtocol>(&wp_fifo_manager_v1_interface, 1, \"Fifo\");\n\n    if (*PENABLECT)\n        PROTO::commitTiming = makeUnique<CCommitTimingProtocol>(&wp_commit_timing_manager_v1_interface, 1, \"CommitTiming\");\n\n    // Screensharing Protocols\n    PROTO::screencopy         = makeUnique<CScreencopyProtocol>(&zwlr_screencopy_manager_v1_interface, 3, \"Screencopy\");\n    PROTO::toplevelExport     = makeUnique<CToplevelExportProtocol>(&hyprland_toplevel_export_manager_v1_interface, 2, \"ToplevelExport\");\n    PROTO::imageCaptureSource = makeUnique<CImageCaptureSourceProtocol>(); // ctor inits actual protos, output and toplevel\n    PROTO::imageCopyCapture   = makeUnique<CImageCopyCaptureProtocol>(&ext_image_copy_capture_manager_v1_interface, 1, \"ImageCopyCapture\");\n\n    if (*PENABLECM)\n        PROTO::colorManagement = makeUnique<CColorManagementProtocol>(&wp_color_manager_v1_interface, 1, \"ColorManagement\", *PDEBUGCM);\n\n    // ! please read the top of this file before adding another protocol\n\n    for (auto const& b : g_pCompositor->m_aqBackend->getImplementations()) {\n        if (b->type() != Aquamarine::AQ_BACKEND_DRM)\n            continue;\n\n        auto lease = makeShared<CDRMLeaseProtocol>(&wp_drm_lease_device_v1_interface, 1, \"DRMLease\", b);\n        if (lease->good())\n            PROTO::lease.emplace(lease->getDeviceName(), lease);\n        else\n            lease.reset();\n\n        if (g_pHyprRenderer->explicitSyncSupported() && !PROTO::sync) {\n            if (g_pCompositor->supportsDrmSyncobjTimeline()) {\n                PROTO::sync = makeUnique<CDRMSyncobjProtocol>(&wp_linux_drm_syncobj_manager_v1_interface, 1, \"DRMSyncobj\");\n                Log::logger->log(Log::DEBUG, \"DRM Syncobj Timeline support detected, enabling explicit sync protocol\");\n            } else\n                Log::logger->log(Log::WARN, \"DRM Syncobj Timeline not supported, skipping explicit sync protocol\");\n        }\n    }\n\n    if (!g_pHyprRenderer->getDRMFormats().empty()) {\n        PROTO::mesaDRM  = makeUnique<CMesaDRMProtocol>(&wl_drm_interface, 2, \"MesaDRM\");\n        PROTO::linuxDma = makeUnique<CLinuxDMABufV1Protocol>(&zwp_linux_dmabuf_v1_interface, 5, \"LinuxDMABUF\");\n    } else\n        Log::logger->log(Log::WARN, \"ProtocolManager: Not binding linux-dmabuf and MesaDRM: DMABUF not available\");\n}\n\nCProtocolManager::~CProtocolManager() {\n    // this is dumb but i don't want to replace all 600 PROTO with the right thing\n\n    // Output\n    PROTO::outputs.clear();\n\n    // Core\n    PROTO::seat.reset();\n    PROTO::data.reset();\n    PROTO::compositor.reset();\n    PROTO::subcompositor.reset();\n    PROTO::shm.reset();\n\n    // Extensions\n    PROTO::viewport.reset();\n    PROTO::tearing.reset();\n    PROTO::fractional.reset();\n    PROTO::xdgOutput.reset();\n    PROTO::cursorShape.reset();\n    PROTO::idleInhibit.reset();\n    PROTO::relativePointer.reset();\n    PROTO::xdgDecoration.reset();\n    PROTO::alphaModifier.reset();\n    PROTO::gamma.reset();\n    PROTO::foreignToplevel.reset();\n    PROTO::pointerGestures.reset();\n    PROTO::foreignToplevelWlr.reset();\n    PROTO::shortcutsInhibit.reset();\n    PROTO::textInputV1.reset();\n    PROTO::textInputV3.reset();\n    PROTO::constraints.reset();\n    PROTO::outputPower.reset();\n    PROTO::activation.reset();\n    PROTO::idle.reset();\n    PROTO::lockNotify.reset();\n    PROTO::sessionLock.reset();\n    PROTO::ime.reset();\n    PROTO::virtualKeyboard.reset();\n    PROTO::virtualPointer.reset();\n    PROTO::outputManagement.reset();\n    PROTO::serverDecorationKDE.reset();\n    PROTO::focusGrab.reset();\n    PROTO::tablet.reset();\n    PROTO::layerShell.reset();\n    PROTO::presentation.reset();\n    PROTO::xdgShell.reset();\n    PROTO::dataWlr.reset();\n    PROTO::primarySelection.reset();\n    PROTO::xwaylandShell.reset();\n    PROTO::screencopy.reset();\n    PROTO::toplevelExport.reset();\n    PROTO::toplevelMapping.reset();\n    PROTO::globalShortcuts.reset();\n    PROTO::xdgDialog.reset();\n    PROTO::singlePixel.reset();\n    PROTO::securityContext.reset();\n    PROTO::ctm.reset();\n    PROTO::hyprlandSurface.reset();\n    PROTO::contentType.reset();\n    PROTO::colorManagement.reset();\n    PROTO::xdgTag.reset();\n    PROTO::xdgBell.reset();\n    PROTO::extWorkspace.reset();\n    PROTO::extDataDevice.reset();\n    PROTO::pointerWarp.reset();\n    PROTO::fifo.reset();\n    PROTO::commitTiming.reset();\n    PROTO::imageCaptureSource.reset();\n\n    for (auto& [_, lease] : PROTO::lease) {\n        lease.reset();\n    }\n    PROTO::sync.reset();\n    PROTO::mesaDRM.reset();\n    PROTO::linuxDma.reset();\n}\n\nbool CProtocolManager::isGlobalPrivileged(const wl_global* global) {\n    if (!global)\n        return false;\n\n    for (auto& [k, v] : PROTO::outputs) {\n        if (global == v->getGlobal())\n            return false;\n    }\n\n    // this is a static whitelist of allowed protocols,\n    // outputs are dynamic so we checked them above\n    // clang-format off\n    static const std::vector<wl_global*> ALLOWED_WHITELIST = {\n        PROTO::seat->getGlobal(),\n        PROTO::data->getGlobal(),\n        PROTO::compositor->getGlobal(),\n        PROTO::subcompositor->getGlobal(),\n        PROTO::shm->getGlobal(),\n        PROTO::viewport->getGlobal(),\n        PROTO::tearing->getGlobal(),\n        PROTO::fractional->getGlobal(),\n        PROTO::cursorShape->getGlobal(),\n        PROTO::idleInhibit->getGlobal(),\n        PROTO::relativePointer->getGlobal(),\n        PROTO::xdgDecoration->getGlobal(),\n        PROTO::alphaModifier->getGlobal(),\n        PROTO::pointerGestures->getGlobal(),\n        PROTO::shortcutsInhibit->getGlobal(),\n        PROTO::textInputV1->getGlobal(),\n        PROTO::textInputV3->getGlobal(),\n        PROTO::constraints->getGlobal(),\n        PROTO::activation->getGlobal(),\n        PROTO::idle->getGlobal(),\n        PROTO::serverDecorationKDE->getGlobal(),\n        PROTO::tablet->getGlobal(),\n        PROTO::presentation->getGlobal(),\n        PROTO::xdgShell->getGlobal(),\n        PROTO::xdgDialog->getGlobal(),\n        PROTO::singlePixel->getGlobal(),\n        PROTO::primarySelection->getGlobal(),\n\t\tPROTO::hyprlandSurface->getGlobal(),\n\t\tPROTO::xdgTag->getGlobal(),\n\t\tPROTO::xdgBell->getGlobal(),\n        PROTO::fifo->getGlobal(),\n        PROTO::commitTiming->getGlobal(),\n        PROTO::sync     ? PROTO::sync->getGlobal()      : nullptr,\n        PROTO::mesaDRM  ? PROTO::mesaDRM->getGlobal()   : nullptr,\n        PROTO::linuxDma ? PROTO::linuxDma->getGlobal()  : nullptr,\n\tPROTO::colorManagement ? PROTO::colorManagement->getGlobal() : nullptr,\n    };\n    // clang-format on\n\n    return std::ranges::find(ALLOWED_WHITELIST, global) == ALLOWED_WHITELIST.end();\n}\n"
  },
  {
    "path": "src/managers/ProtocolManager.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include <unordered_map>\n\nclass CProtocolManager {\n  public:\n    CProtocolManager();\n    ~CProtocolManager();\n\n    bool isGlobalPrivileged(const wl_global* global);\n\n  private:\n    std::unordered_map<std::string, CHyprSignalListener> m_modeChangeListeners;\n\n    void                                                 onMonitorModeChange(PHLMONITOR pMonitor);\n};\n\ninline UP<CProtocolManager> g_pProtocolManager;\n"
  },
  {
    "path": "src/managers/SeatManager.cpp",
    "content": "#include \"SeatManager.hpp\"\n#include \"../protocols/core/Seat.hpp\"\n#include \"../protocols/core/DataDevice.hpp\"\n#include \"../protocols/DataDeviceWlr.hpp\"\n#include \"../protocols/ExtDataDevice.hpp\"\n#include \"../protocols/PrimarySelection.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"wlr-layer-shell-unstable-v1.hpp\"\n#include <algorithm>\n#include <hyprutils/utils/ScopeGuard.hpp>\n#include <ranges>\n#include <cstring>\n\nusing namespace Hyprutils::Utils;\n\nCSeatManager::CSeatManager() {\n    m_listeners.newSeatResource = PROTO::seat->m_events.newSeatResource.listen([this](const auto& resource) { onNewSeatResource(resource); });\n}\n\nCSeatManager::SSeatResourceContainer::SSeatResourceContainer(SP<CWLSeatResource> res) : resource(res) {\n    listeners.destroy = res->m_events.destroy.listen(\n        [this] { std::erase_if(g_pSeatManager->m_seatResources, [this](const auto& e) { return e->resource.expired() || e->resource == resource; }); });\n}\n\nvoid CSeatManager::onNewSeatResource(SP<CWLSeatResource> resource) {\n    m_seatResources.emplace_back(makeShared<SSeatResourceContainer>(resource));\n}\n\nSP<CSeatManager::SSeatResourceContainer> CSeatManager::containerForResource(SP<CWLSeatResource> seatResource) {\n    for (auto const& c : m_seatResources) {\n        if (c->resource == seatResource)\n            return c;\n    }\n\n    return nullptr;\n}\n\nuint32_t CSeatManager::nextSerial(SP<CWLSeatResource> seatResource) {\n    if (!seatResource)\n        return 0;\n\n    auto container = containerForResource(seatResource);\n\n    ASSERT(container);\n\n    auto serial = wl_display_next_serial(g_pCompositor->m_wlDisplay);\n\n    container->serials.emplace_back(serial);\n\n    if (container->serials.size() > MAX_SERIAL_STORE_LEN)\n        container->serials.erase(container->serials.begin());\n\n    return serial;\n}\n\nbool CSeatManager::serialValid(SP<CWLSeatResource> seatResource, uint32_t serial, bool erase) {\n    if (!seatResource)\n        return false;\n\n    auto container = containerForResource(seatResource);\n\n    ASSERT(container);\n\n    for (auto it = container->serials.begin(); it != container->serials.end(); ++it) {\n        if (*it == serial) {\n            if (erase)\n                container->serials.erase(it);\n            return true;\n        }\n    }\n\n    return false;\n}\n\nvoid CSeatManager::updateCapabilities(uint32_t capabilities) {\n    PROTO::seat->updateCapabilities(capabilities);\n}\n\nvoid CSeatManager::setMouse(SP<IPointer> MAUZ) {\n    if (m_mouse == MAUZ)\n        return;\n\n    m_mouse = MAUZ;\n}\n\nvoid CSeatManager::setKeyboard(SP<IKeyboard> KEEB) {\n    if (m_keyboard == KEEB)\n        return;\n\n    if (m_keyboard)\n        m_keyboard->m_active = false;\n    m_keyboard = KEEB;\n\n    if (KEEB)\n        KEEB->m_active = true;\n\n    updateActiveKeyboardData();\n}\n\nvoid CSeatManager::updateActiveKeyboardData() {\n    if (m_keyboard)\n        PROTO::seat->updateRepeatInfo(m_keyboard->m_repeatRate, m_keyboard->m_repeatDelay);\n    PROTO::seat->updateKeymap();\n}\n\nvoid CSeatManager::setKeyboardFocus(SP<CWLSurfaceResource> surf) {\n    if (m_state.keyboardFocus == surf)\n        return;\n\n    if (!m_keyboard) {\n        Log::logger->log(Log::ERR, \"BUG THIS: setKeyboardFocus without a valid keyboard set\");\n        return;\n    }\n\n    m_listeners.keyboardSurfaceDestroy.reset();\n\n    if (m_state.keyboardFocusResource) {\n        auto client = m_state.keyboardFocusResource->client();\n        for (auto const& s : m_seatResources) {\n            if (s->resource->client() != client)\n                continue;\n\n            for (auto const& k : s->resource->m_keyboards) {\n                if (!k)\n                    continue;\n\n                k->sendMods(0, m_keyboard->m_modifiersState.latched, m_keyboard->m_modifiersState.locked, m_keyboard->m_modifiersState.group);\n                k->sendLeave();\n            }\n        }\n    }\n\n    m_state.keyboardFocusResource.reset();\n    m_state.keyboardFocus = surf;\n\n    if (!surf) {\n        m_events.keyboardFocusChange.emit();\n        return;\n    }\n\n    wl_array keys;\n    wl_array_init(&keys);\n    CScopeGuard x([&keys] { wl_array_release(&keys); });\n\n    const auto& PRESSED = g_pInputManager->getKeysFromAllKBs();\n    static_assert(std::is_same_v<std::decay_t<decltype(PRESSED)>::value_type, uint32_t>, \"Element type different from keycode type uint32_t\");\n\n    const auto PRESSEDARRSIZE = PRESSED.size() * sizeof(uint32_t);\n    const auto PKEYS          = wl_array_add(&keys, PRESSEDARRSIZE);\n    if (PKEYS)\n        memcpy(PKEYS, PRESSED.data(), PRESSEDARRSIZE);\n\n    auto client = surf->client();\n    for (auto const& r : m_seatResources | std::views::reverse) {\n        if (r->resource->client() != client)\n            continue;\n\n        m_state.keyboardFocusResource = r->resource;\n        for (auto const& k : r->resource->m_keyboards) {\n            if (!k)\n                continue;\n\n            k->sendEnter(surf, &keys);\n            k->sendMods(m_keyboard->m_modifiersState.depressed, m_keyboard->m_modifiersState.latched, m_keyboard->m_modifiersState.locked, m_keyboard->m_modifiersState.group);\n        }\n    }\n\n    m_listeners.keyboardSurfaceDestroy = surf->m_events.destroy.listen([this] { setKeyboardFocus(nullptr); });\n\n    m_events.keyboardFocusChange.emit();\n}\n\nvoid CSeatManager::sendKeyboardKey(uint32_t timeMs, uint32_t key, wl_keyboard_key_state state_) {\n    if (!m_state.keyboardFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.keyboardFocusResource->client())\n            continue;\n\n        for (auto const& k : s->resource->m_keyboards) {\n            if (!k)\n                continue;\n\n            k->sendKey(timeMs, key, state_);\n        }\n    }\n}\n\nvoid CSeatManager::sendKeyboardMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {\n    if (!m_state.keyboardFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.keyboardFocusResource->client())\n            continue;\n\n        for (auto const& k : s->resource->m_keyboards) {\n            if (!k)\n                continue;\n\n            k->sendMods(depressed, latched, locked, group);\n        }\n    }\n}\n\nvoid CSeatManager::setPointerFocus(SP<CWLSurfaceResource> surf, const Vector2D& local) {\n    if (m_state.pointerFocus == surf)\n        return;\n\n    if (PROTO::data->dndActive() && surf) {\n        if (m_state.dndPointerFocus == surf)\n            return;\n        Log::logger->log(Log::DEBUG, \"[seatmgr] Refusing pointer focus during an active dnd, but setting dndPointerFocus\");\n        m_state.dndPointerFocus = surf;\n        m_events.dndPointerFocusChange.emit();\n        return;\n    }\n\n    if (!m_mouse) {\n        Log::logger->log(Log::ERR, \"BUG THIS: setPointerFocus without a valid mouse set\");\n        return;\n    }\n\n    m_listeners.pointerSurfaceDestroy.reset();\n\n    if (m_state.pointerFocusResource) {\n        auto client = m_state.pointerFocusResource->client();\n        for (auto const& s : m_seatResources) {\n            if (s->resource->client() != client)\n                continue;\n\n            for (auto const& p : s->resource->m_pointers) {\n                if (!p)\n                    continue;\n\n                p->sendLeave();\n            }\n        }\n    }\n\n    auto lastPointerFocusResource = m_state.pointerFocusResource;\n\n    m_state.dndPointerFocus.reset();\n    m_state.pointerFocusResource.reset();\n    m_state.pointerFocus = surf;\n\n    if (!surf) {\n        sendPointerFrame(lastPointerFocusResource);\n        m_events.pointerFocusChange.emit();\n        return;\n    }\n\n    m_state.dndPointerFocus = surf;\n\n    auto client = surf->client();\n    for (auto const& r : m_seatResources | std::views::reverse) {\n        if (r->resource->client() != client)\n            continue;\n\n        m_state.pointerFocusResource = r->resource;\n        for (auto const& p : r->resource->m_pointers) {\n            if (!p)\n                continue;\n\n            p->sendEnter(surf, local);\n        }\n    }\n\n    if (m_state.pointerFocusResource != lastPointerFocusResource)\n        sendPointerFrame(lastPointerFocusResource);\n\n    sendPointerFrame();\n\n    m_listeners.pointerSurfaceDestroy = surf->m_events.destroy.listen([this] { setPointerFocus(nullptr, {}); });\n\n    m_events.pointerFocusChange.emit();\n    m_events.dndPointerFocusChange.emit();\n}\n\nvoid CSeatManager::sendPointerMotion(uint32_t timeMs, const Vector2D& local) {\n    if (!m_state.pointerFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.pointerFocusResource->client())\n            continue;\n\n        for (auto const& p : s->resource->m_pointers) {\n            if (!p)\n                continue;\n\n            p->sendMotion(timeMs, local);\n        }\n    }\n\n    m_lastLocalCoords = local;\n}\n\nvoid CSeatManager::sendPointerButton(uint32_t timeMs, uint32_t key, wl_pointer_button_state state_) {\n    if (!m_state.pointerFocusResource || PROTO::data->dndActive())\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.pointerFocusResource->client())\n            continue;\n\n        for (auto const& p : s->resource->m_pointers) {\n            if (!p)\n                continue;\n\n            p->sendButton(timeMs, key, state_);\n        }\n    }\n}\n\nvoid CSeatManager::sendPointerFrame() {\n    if (!m_state.pointerFocusResource)\n        return;\n\n    sendPointerFrame(m_state.pointerFocusResource);\n}\n\nvoid CSeatManager::sendPointerFrame(WP<CWLSeatResource> pResource) {\n    if (!pResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != pResource->client())\n            continue;\n\n        for (auto const& p : s->resource->m_pointers) {\n            if (!p)\n                continue;\n\n            p->sendFrame();\n        }\n    }\n}\n\nvoid CSeatManager::sendPointerAxis(uint32_t timeMs, wl_pointer_axis axis, double value, int32_t discrete, int32_t value120, wl_pointer_axis_source source,\n                                   wl_pointer_axis_relative_direction relative) {\n    if (!m_state.pointerFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.pointerFocusResource->client())\n            continue;\n\n        for (auto const& p : s->resource->m_pointers) {\n            if (!p)\n                continue;\n\n            p->sendAxis(timeMs, axis, value);\n            p->sendAxisSource(source);\n            p->sendAxisRelativeDirection(axis, relative);\n\n            if (source == 0) {\n                if (p->version() >= 8)\n                    p->sendAxisValue120(axis, value120);\n                else\n                    p->sendAxisDiscrete(axis, discrete);\n            } else if (value == 0)\n                p->sendAxisStop(timeMs, axis);\n        }\n    }\n}\n\nvoid CSeatManager::sendTouchDown(SP<CWLSurfaceResource> surf, uint32_t timeMs, int32_t id, const Vector2D& local) {\n    m_listeners.touchSurfaceDestroy.reset();\n\n    m_state.touchFocusResource.reset();\n    m_state.touchFocus = surf;\n\n    auto client = surf->client();\n    for (auto const& r : m_seatResources | std::views::reverse) {\n        if (r->resource->client() != client)\n            continue;\n\n        m_state.touchFocusResource = r->resource;\n        for (auto const& t : r->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendDown(surf, timeMs, id, local);\n        }\n    }\n\n    m_listeners.touchSurfaceDestroy = surf->m_events.destroy.listen([this, timeMs, id] { sendTouchUp(timeMs + 10, id); });\n\n    m_touchLocks++;\n\n    if (m_touchLocks <= 1)\n        m_events.touchFocusChange.emit();\n}\n\nvoid CSeatManager::sendTouchUp(uint32_t timeMs, int32_t id) {\n    if (!m_state.touchFocusResource || m_touchLocks <= 0)\n        return;\n\n    auto client = m_state.touchFocusResource->client();\n    for (auto const& r : m_seatResources | std::views::reverse) {\n        if (r->resource->client() != client)\n            continue;\n\n        m_state.touchFocusResource = r->resource;\n        for (auto const& t : r->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendUp(timeMs, id);\n        }\n    }\n\n    m_touchLocks--;\n\n    if (m_touchLocks <= 0)\n        m_events.touchFocusChange.emit();\n}\n\nvoid CSeatManager::sendTouchMotion(uint32_t timeMs, int32_t id, const Vector2D& local) {\n    if (!m_state.touchFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.touchFocusResource->client())\n            continue;\n\n        for (auto const& t : s->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendMotion(timeMs, id, local);\n        }\n    }\n}\n\nvoid CSeatManager::sendTouchFrame() {\n    if (!m_state.touchFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.touchFocusResource->client())\n            continue;\n\n        for (auto const& t : s->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendFrame();\n        }\n    }\n}\n\nvoid CSeatManager::sendTouchCancel() {\n    if (!m_state.touchFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.touchFocusResource->client())\n            continue;\n\n        for (auto const& t : s->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendCancel();\n        }\n    }\n}\n\nvoid CSeatManager::sendTouchShape(int32_t id, const Vector2D& shape) {\n    if (!m_state.touchFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.touchFocusResource->client())\n            continue;\n\n        for (auto const& t : s->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendShape(id, shape);\n        }\n    }\n}\n\nvoid CSeatManager::sendTouchOrientation(int32_t id, double angle) {\n    if (!m_state.touchFocusResource)\n        return;\n\n    for (auto const& s : m_seatResources) {\n        if (s->resource->client() != m_state.touchFocusResource->client())\n            continue;\n\n        for (auto const& t : s->resource->m_touches) {\n            if (!t)\n                continue;\n\n            t->sendOrientation(id, angle);\n        }\n    }\n}\n\nvoid CSeatManager::refocusGrab() {\n    if (!m_seatGrab)\n        return;\n\n    if (!m_seatGrab->m_surfs.empty()) {\n        // try to find a surf in focus first\n        const auto MOUSE = g_pInputManager->getMouseCoordsInternal();\n        for (auto const& s : m_seatGrab->m_surfs) {\n            auto hlSurf = Desktop::View::CWLSurface::fromResource(s.lock());\n            if (!hlSurf)\n                continue;\n\n            auto b = hlSurf->getSurfaceBoxGlobal();\n            if (!b.has_value())\n                continue;\n\n            if (!b->containsPoint(MOUSE))\n                continue;\n\n            if (m_seatGrab->m_keyboard)\n                setKeyboardFocus(s.lock());\n            if (m_seatGrab->m_pointer)\n                setPointerFocus(s.lock(), MOUSE - b->pos());\n            return;\n        }\n\n        SP<CWLSurfaceResource> surf = m_seatGrab->m_surfs.at(0).lock();\n        if (m_seatGrab->m_keyboard)\n            setKeyboardFocus(surf);\n        if (m_seatGrab->m_pointer)\n            setPointerFocus(surf, {});\n    }\n}\n\nvoid CSeatManager::onSetCursor(SP<CWLSeatResource> seatResource, uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& hotspot) {\n    if (!m_state.pointerFocusResource || !seatResource || seatResource->client() != m_state.pointerFocusResource->client()) {\n        Log::logger->log(Log::DEBUG, \"[seatmgr] Rejecting a setCursor because the client ain't in focus\");\n        return;\n    }\n\n    // TODO: fix this. Probably should be done in the CWlPointer as the serial could be lost by us.\n    // if (!serialValid(seatResource, serial)) {\n    //     Log::logger->log(Log::DEBUG, \"[seatmgr] Rejecting a setCursor because the serial is invalid\");\n    //     return;\n    // }\n\n    m_events.setCursor.emit(SSetCursorEvent{surf, hotspot});\n}\n\nSP<CWLSeatResource> CSeatManager::seatResourceForClient(wl_client* client) {\n    return PROTO::seat->seatResourceForClient(client);\n}\n\nvoid CSeatManager::setCurrentSelection(SP<IDataSource> source) {\n    if (source == m_selection.currentSelection) {\n        Log::logger->log(Log::WARN, \"[seat] duplicated setCurrentSelection?\");\n        return;\n    }\n\n    m_selection.destroySelection.reset();\n\n    if (m_selection.currentSelection)\n        m_selection.currentSelection->cancelled();\n\n    if (!source)\n        PROTO::data->setSelection(nullptr);\n\n    m_selection.currentSelection = source;\n\n    if (source) {\n        m_selection.destroySelection = source->m_events.destroy.listen([this] { setCurrentSelection(nullptr); });\n        PROTO::data->setSelection(source);\n        PROTO::dataWlr->setSelection(source, false);\n        PROTO::extDataDevice->setSelection(source, false);\n    }\n\n    m_events.setSelection.emit();\n}\n\nvoid CSeatManager::setCurrentPrimarySelection(SP<IDataSource> source) {\n    if (source == m_selection.currentPrimarySelection) {\n        Log::logger->log(Log::WARN, \"[seat] duplicated setCurrentPrimarySelection?\");\n        return;\n    }\n\n    m_selection.destroyPrimarySelection.reset();\n\n    if (m_selection.currentPrimarySelection)\n        m_selection.currentPrimarySelection->cancelled();\n\n    if (!source)\n        PROTO::primarySelection->setSelection(nullptr);\n\n    m_selection.currentPrimarySelection = source;\n\n    if (source) {\n        m_selection.destroyPrimarySelection = source->m_events.destroy.listen([this] { setCurrentPrimarySelection(nullptr); });\n        PROTO::primarySelection->setSelection(source);\n        PROTO::dataWlr->setSelection(source, true);\n        PROTO::extDataDevice->setSelection(source, true);\n    }\n\n    m_events.setPrimarySelection.emit();\n}\n\nvoid CSeatManager::setGrab(SP<CSeatGrab> grab) {\n    if (m_seatGrab) {\n        auto oldGrab = m_seatGrab;\n\n        // Try to find the parent window or layer surface from the grab\n        PHLWINDOW parentWindow;\n        PHLLS     parentLayer;\n        if (oldGrab && oldGrab->m_surfs.size()) {\n            // Try to find the surface that had focus when the grab ended\n            SP<CWLSurfaceResource> focusedSurf;\n            auto                   keyboardFocus = m_state.keyboardFocus.lock();\n            auto                   pointerFocus  = m_state.pointerFocus.lock();\n\n            // Check if keyboard or pointer focus is in the grab\n            for (auto const& s : oldGrab->m_surfs) {\n                auto surf = s.lock();\n                if (surf && (surf == keyboardFocus || surf == pointerFocus)) {\n                    focusedSurf = surf;\n                    break;\n                }\n            }\n\n            // Fall back to first surface if no focused surface found\n            if (!focusedSurf)\n                focusedSurf = oldGrab->m_surfs.front().lock();\n\n            if (focusedSurf) {\n                auto hlSurface = Desktop::View::CWLSurface::fromResource(focusedSurf);\n                if (hlSurface) {\n                    auto popup = Desktop::View::CPopup::fromView(hlSurface->view());\n                    if (popup) {\n                        auto t1Owner = popup->getT1Owner();\n                        if (t1Owner) {\n                            parentWindow = Desktop::View::CWindow::fromView(t1Owner->view());\n                            if (!parentWindow)\n                                parentLayer = Desktop::View::CLayerSurface::fromView(t1Owner->view());\n                        }\n                    }\n                }\n            }\n        }\n\n        m_seatGrab.reset();\n\n        if (parentLayer && parentLayer->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) {\n            Desktop::focusState()->rawSurfaceFocus(parentLayer->wlSurface()->resource());\n        } else {\n            static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n            if (*PFOLLOWMOUSE == 0 || *PFOLLOWMOUSE == 2 || *PFOLLOWMOUSE == 3) {\n                const auto PMONITOR = g_pCompositor->getMonitorFromCursor();\n\n                // If this was a popup grab, focus its parent window to maintain context\n                if (validMapped(parentWindow)) {\n                    Desktop::focusState()->rawWindowFocus(parentWindow, Desktop::FOCUS_REASON_FFM);\n                    Log::logger->log(Log::DEBUG, \"[seatmgr] Refocused popup parent window {} (follow_mouse={})\", parentWindow->m_title, *PFOLLOWMOUSE);\n                } else\n                    g_pInputManager->refocusLastWindow(PMONITOR);\n            } else\n                g_pInputManager->refocus();\n        }\n\n        auto                          currentFocus = m_state.keyboardFocus.lock();\n        auto                          refocus      = !currentFocus;\n\n        SP<Desktop::View::CWLSurface> surf;\n        PHLLS                         layer;\n\n        if (!refocus) {\n            surf  = Desktop::View::CWLSurface::fromResource(currentFocus);\n            layer = surf ? Desktop::View::CLayerSurface::fromView(surf->view()) : nullptr;\n        }\n\n        if (!refocus && !layer) {\n            auto popup = surf ? Desktop::View::CPopup::fromView(surf->view()) : nullptr;\n            if (popup) {\n                auto parent = popup->getT1Owner();\n                layer       = Desktop::View::CLayerSurface::fromView(parent->view());\n            }\n        }\n\n        if (!refocus && layer)\n            refocus = layer->m_interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;\n\n        if (refocus) {\n            auto candidate = Desktop::focusState()->window();\n\n            if (candidate)\n                Desktop::focusState()->rawWindowFocus(candidate, Desktop::FOCUS_REASON_FFM);\n        }\n\n        if (oldGrab->m_onEnd)\n            oldGrab->m_onEnd();\n    }\n\n    if (!grab)\n        return;\n\n    m_seatGrab = grab;\n\n    refocusGrab();\n}\n\nvoid CSeatManager::resendEnterEvents() {\n    SP<CWLSurfaceResource> kb = m_state.keyboardFocus.lock();\n    SP<CWLSurfaceResource> pt = m_state.pointerFocus.lock();\n\n    auto                   last = m_lastLocalCoords;\n\n    setKeyboardFocus(nullptr);\n    setPointerFocus(nullptr, {});\n\n    setKeyboardFocus(kb);\n    setPointerFocus(pt, last);\n}\n\nbool CSeatGrab::accepts(SP<CWLSurfaceResource> surf) {\n    return std::ranges::find(m_surfs, surf) != m_surfs.end();\n}\n\nvoid CSeatGrab::add(SP<CWLSurfaceResource> surf) {\n    m_surfs.emplace_back(surf);\n}\n\nvoid CSeatGrab::remove(SP<CWLSurfaceResource> surf) {\n    std::erase(m_surfs, surf);\n    if ((m_keyboard && g_pSeatManager->m_state.keyboardFocus == surf) || (m_pointer && g_pSeatManager->m_state.pointerFocus == surf))\n        g_pSeatManager->refocusGrab();\n}\n\nvoid CSeatGrab::setCallback(std::function<void()> onEnd_) {\n    m_onEnd = onEnd_;\n}\n\nvoid CSeatGrab::clear() {\n    m_surfs.clear();\n}\n"
  },
  {
    "path": "src/managers/SeatManager.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <wayland-server-protocol.h>\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../protocols/types/DataDevice.hpp\"\n#include <vector>\n\nconstexpr size_t MAX_SERIAL_STORE_LEN = 100;\n\nclass CWLSurfaceResource;\nclass CWLSeatResource;\nclass IPointer;\nclass IKeyboard;\n\n/*\n    A seat grab defines a restricted set of surfaces that can be focused.\n    Only one grab can be active at a time\n\n    when a grab is removed, refocus() will happen\n\n    Different from a constraint.\n\n    When first set with setGrab, SeatManager will try to find a surface that is at the mouse pointer to focus,\n    from first added to last added. If none are, first is focused.\n*/\nclass CSeatGrab {\n  public:\n    bool accepts(SP<CWLSurfaceResource> surf);\n    void add(SP<CWLSurfaceResource> surf);\n    void remove(SP<CWLSurfaceResource> surf);\n    void setCallback(std::function<void()> onEnd_);\n    void clear();\n\n    bool m_keyboard = false;\n    bool m_pointer  = false;\n\n  private:\n    std::vector<WP<CWLSurfaceResource>> m_surfs;\n    std::function<void()>               m_onEnd;\n    friend class CSeatManager;\n};\n\nclass CSeatManager {\n  public:\n    CSeatManager();\n\n    void     updateCapabilities(uint32_t capabilities); // in IHID caps\n\n    void     setMouse(SP<IPointer> mouse);\n    void     setKeyboard(SP<IKeyboard> keeb);\n    void     updateActiveKeyboardData(); // updates the clients with the keymap and repeat info\n\n    void     setKeyboardFocus(SP<CWLSurfaceResource> surf);\n    void     sendKeyboardKey(uint32_t timeMs, uint32_t key, wl_keyboard_key_state state);\n    void     sendKeyboardMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group);\n\n    void     setPointerFocus(SP<CWLSurfaceResource> surf, const Vector2D& local);\n    void     sendPointerMotion(uint32_t timeMs, const Vector2D& local);\n    void     sendPointerButton(uint32_t timeMs, uint32_t key, wl_pointer_button_state state);\n    void     sendPointerFrame();\n    void     sendPointerFrame(WP<CWLSeatResource> pResource);\n    void     sendPointerAxis(uint32_t timeMs, wl_pointer_axis axis, double value, int32_t discrete, int32_t value120, wl_pointer_axis_source source,\n                             wl_pointer_axis_relative_direction relative);\n\n    void     sendTouchDown(SP<CWLSurfaceResource> surf, uint32_t timeMs, int32_t id, const Vector2D& local);\n    void     sendTouchUp(uint32_t timeMs, int32_t id);\n    void     sendTouchMotion(uint32_t timeMs, int32_t id, const Vector2D& local);\n    void     sendTouchFrame();\n    void     sendTouchCancel();\n    void     sendTouchShape(int32_t id, const Vector2D& shape);\n    void     sendTouchOrientation(int32_t id, double angle);\n\n    void     resendEnterEvents();\n\n    uint32_t nextSerial(SP<CWLSeatResource> seatResource);\n    // pops the serial if it was valid, meaning it is consumed.\n    bool                serialValid(SP<CWLSeatResource> seatResource, uint32_t serial, bool erase = true);\n\n    void                onSetCursor(SP<CWLSeatResource> seatResource, uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& hotspot);\n\n    SP<CWLSeatResource> seatResourceForClient(wl_client* client);\n\n    struct {\n        WP<CWLSurfaceResource> keyboardFocus;\n        WP<CWLSeatResource>    keyboardFocusResource;\n\n        WP<CWLSurfaceResource> pointerFocus;\n        WP<CWLSeatResource>    pointerFocusResource;\n\n        WP<CWLSurfaceResource> touchFocus;\n        WP<CWLSeatResource>    touchFocusResource;\n\n        WP<CWLSurfaceResource> dndPointerFocus;\n    } m_state;\n\n    struct SSetCursorEvent {\n        SP<CWLSurfaceResource> surf = nullptr;\n        Vector2D               hotspot;\n    };\n\n    struct {\n        CSignalT<>                keyboardFocusChange;\n        CSignalT<>                pointerFocusChange;\n        CSignalT<>                dndPointerFocusChange;\n        CSignalT<>                touchFocusChange;\n        CSignalT<SSetCursorEvent> setCursor;\n        CSignalT<>                setSelection;\n        CSignalT<>                setPrimarySelection;\n    } m_events;\n\n    struct {\n        WP<IDataSource>     currentSelection;\n        CHyprSignalListener destroySelection;\n        WP<IDataSource>     currentPrimarySelection;\n        CHyprSignalListener destroyPrimarySelection;\n    } m_selection;\n\n    void setCurrentSelection(SP<IDataSource> source);\n    void setCurrentPrimarySelection(SP<IDataSource> source);\n\n    // do not write to directly, use set...\n    WP<IPointer>  m_mouse;\n    WP<IKeyboard> m_keyboard;\n\n    void          setGrab(SP<CSeatGrab> grab); // nullptr removes\n    SP<CSeatGrab> m_seatGrab;\n\n  private:\n    struct SSeatResourceContainer {\n        SSeatResourceContainer(SP<CWLSeatResource>);\n\n        WP<CWLSeatResource>   resource;\n        std::vector<uint32_t> serials; // old -> new\n\n        struct {\n            CHyprSignalListener destroy;\n        } listeners;\n    };\n\n    std::vector<SP<SSeatResourceContainer>> m_seatResources;\n    void                                    onNewSeatResource(SP<CWLSeatResource> resource);\n    SP<SSeatResourceContainer>              containerForResource(SP<CWLSeatResource> seatResource);\n\n    void                                    refocusGrab();\n\n    struct {\n        CHyprSignalListener newSeatResource;\n        CHyprSignalListener keyboardSurfaceDestroy;\n        CHyprSignalListener pointerSurfaceDestroy;\n        CHyprSignalListener touchSurfaceDestroy;\n    } m_listeners;\n\n    Vector2D m_lastLocalCoords;\n    int      m_touchLocks = 0; // we assume there aint like 20 touch devices at once...\n\n    friend struct SSeatResourceContainer;\n    friend class CSeatGrab;\n};\n\ninline UP<CSeatManager> g_pSeatManager;\n"
  },
  {
    "path": "src/managers/SessionLockManager.cpp",
    "content": "#include \"SessionLockManager.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../protocols/FractionalScale.hpp\"\n#include \"../protocols/SessionLock.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../desktop/view/SessionLock.hpp\"\n#include \"./managers/SeatManager.hpp\"\n#include \"./managers/input/InputManager.hpp\"\n#include \"./managers/eventLoop/EventLoopManager.hpp\"\n#include <algorithm>\n#include <ranges>\n\nSSessionLockSurface::SSessionLockSurface(SP<CSessionLockSurface> surface_) : surface(surface_) {\n    pWlrSurface = surface->surface();\n\n    listeners.map = surface_->m_events.map.listen([this] {\n        mapped = true;\n\n        g_pInputManager->simulateMouseMovement();\n\n        const auto PMONITOR = g_pCompositor->getMonitorFromID(iMonitorID);\n\n        if (PMONITOR)\n            g_pHyprRenderer->damageMonitor(PMONITOR);\n    });\n\n    listeners.destroy = surface_->m_events.destroy.listen([this] {\n        if (pWlrSurface == Desktop::focusState()->surface())\n            Desktop::focusState()->surface().reset();\n\n        g_pSessionLockManager->removeSessionLockSurface(this);\n    });\n\n    listeners.commit = surface_->m_events.commit.listen([this] {\n        const auto PMONITOR = g_pCompositor->getMonitorFromID(iMonitorID);\n\n        if (mapped && !Desktop::focusState()->surface())\n            g_pInputManager->simulateMouseMovement();\n\n        if (PMONITOR)\n            g_pHyprRenderer->damageMonitor(PMONITOR);\n    });\n}\n\nCSessionLockManager::CSessionLockManager() {\n    m_listeners.newLock = PROTO::sessionLock->m_events.newLock.listen([this](const auto& lock) { this->onNewSessionLock(lock); });\n}\n\nvoid CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {\n    static auto PALLOWRELOCK = CConfigValue<Hyprlang::INT>(\"misc:allow_session_lock_restore\");\n\n    if (PROTO::sessionLock->isLocked() && !*PALLOWRELOCK) {\n        LOGM(Log::DEBUG, \"Cannot re-lock, misc:allow_session_lock_restore is disabled\");\n        pLock->sendDenied();\n        return;\n    }\n\n    if (m_sessionLock && !clientDenied() && !clientLocked())\n        return; // Not allowing to relock in case the old lock is still in a limbo\n\n    LOGM(Log::DEBUG, \"Session got locked by {:x}\", (uintptr_t)pLock.get());\n\n    m_sessionLock       = makeUnique<SSessionLock>();\n    m_sessionLock->lock = pLock;\n    m_sessionLock->lockTimer.reset();\n\n    m_sessionLock->listeners.newSurface = pLock->m_events.newLockSurface.listen([this](const SP<CSessionLockSurface>& surface) {\n        const auto PMONITOR = surface->monitor();\n\n        const auto NEWSURFACE  = m_sessionLock->vSessionLockSurfaces.emplace_back(makeShared<SSessionLockSurface>(surface));\n        NEWSURFACE->iMonitorID = PMONITOR->m_id;\n        PROTO::fractional->sendScale(surface->surface(), PMONITOR->m_scale);\n\n        g_pCompositor->m_otherViews.emplace_back(Desktop::View::CSessionLock::create(surface));\n    });\n\n    m_sessionLock->listeners.unlock = pLock->m_events.unlockAndDestroy.listen([this] {\n        m_sessionLock.reset();\n        g_pInputManager->refocus();\n\n        for (auto const& m : g_pCompositor->m_monitors)\n            g_pHyprRenderer->damageMonitor(m);\n    });\n\n    m_sessionLock->listeners.destroy = pLock->m_events.destroyed.listen([this] {\n        m_sessionLock.reset();\n        Desktop::focusState()->rawSurfaceFocus(nullptr);\n\n        for (auto const& m : g_pCompositor->m_monitors)\n            g_pHyprRenderer->damageMonitor(m);\n    });\n\n    Desktop::focusState()->rawSurfaceFocus(nullptr);\n    g_pSeatManager->setGrab(nullptr);\n\n    const bool NOACTIVEMONS = std::ranges::all_of(g_pCompositor->m_monitors, [](const auto& m) { return !m->m_enabled || !m->m_dpmsStatus; });\n\n    if (NOACTIVEMONS || g_pCompositor->m_unsafeState) {\n        // Normally the locked event is sent after each output rendered a lock screen frame.\n        // When there are no active outputs, send it right away.\n        m_sessionLock->lock->sendLocked();\n        m_sessionLock->hasSentLocked = true;\n        return;\n    }\n\n    m_sessionLock->sendDeniedTimer = makeShared<CEventLoopTimer>(\n        // Within this arbitrary amount of time, a session-lock client is expected to create and commit a lock surface for each output. If the client fails to do that, it will be denied.\n        std::chrono::seconds(5),\n        [](auto, auto) {\n            if (!g_pSessionLockManager || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied())\n                return;\n\n            if (!g_pSessionLockManager->m_sessionLock || !g_pSessionLockManager->m_sessionLock->lock)\n                return;\n\n            if (g_pCompositor->m_unsafeState || !g_pCompositor->m_aqBackend->hasSession() || !g_pCompositor->m_aqBackend->session->active) {\n                // Because the session is inactive, there is a good reason for why the client did't manage to render to all outputs.\n                // We send locked, although this could lead to imperfect frames when we start to render again.\n                g_pSessionLockManager->m_sessionLock->lock->sendLocked();\n                g_pSessionLockManager->m_sessionLock->hasSentLocked = true;\n                return;\n            }\n\n            LOGM(Log::WARN, \"Kicking lockscreen client, because it failed to render to all outputs within 5 seconds\");\n            g_pSessionLockManager->m_sessionLock->lock->sendDenied();\n            g_pSessionLockManager->m_sessionLock->hasSentDenied = true;\n        },\n        nullptr);\n\n    g_pEventLoopManager->addTimer(m_sessionLock->sendDeniedTimer);\n}\n\nvoid CSessionLockManager::removeSendDeniedTimer() {\n    if (!m_sessionLock || !m_sessionLock->sendDeniedTimer)\n        return;\n\n    g_pEventLoopManager->removeTimer(m_sessionLock->sendDeniedTimer);\n    m_sessionLock->sendDeniedTimer.reset();\n}\n\nbool CSessionLockManager::isSessionLocked() {\n    return PROTO::sessionLock->isLocked();\n}\n\nWP<SSessionLockSurface> CSessionLockManager::getSessionLockSurfaceForMonitor(uint64_t id) {\n    if (!m_sessionLock)\n        return {};\n\n    for (auto const& sls : m_sessionLock->vSessionLockSurfaces) {\n        if (sls->iMonitorID == id) {\n            if (sls->mapped)\n                return sls;\n            else\n                return {};\n        }\n    }\n\n    return {};\n}\n\nvoid CSessionLockManager::onLockscreenRenderedOnMonitor(uint64_t id) {\n    if (!m_sessionLock || m_sessionLock->hasSentLocked || m_sessionLock->hasSentDenied)\n        return;\n\n    m_sessionLock->lockedMonitors.emplace(id);\n    const bool LOCKED =\n        std::ranges::all_of(g_pCompositor->m_monitors, [this](auto m) { return !m->m_enabled || !m->m_dpmsStatus || m_sessionLock->lockedMonitors.contains(m->m_id); });\n\n    if (LOCKED && m_sessionLock->lock->good()) {\n        removeSendDeniedTimer();\n        m_sessionLock->lock->sendLocked();\n        m_sessionLock->hasSentLocked = true;\n    }\n}\n\nbool CSessionLockManager::isSurfaceSessionLock(SP<CWLSurfaceResource> pSurface) {\n    // TODO: this has some edge cases when it's wrong (e.g. destroyed lock but not yet surfaces)\n    // but can be easily fixed when I rewrite wlr_surface\n\n    if (!m_sessionLock)\n        return false;\n\n    for (auto const& sls : m_sessionLock->vSessionLockSurfaces) {\n        if (sls->surface->surface() == pSurface)\n            return true;\n    }\n\n    return false;\n}\n\nbool CSessionLockManager::anySessionLockSurfacesPresent() {\n    return m_sessionLock && std::ranges::any_of(m_sessionLock->vSessionLockSurfaces, [](const auto& surf) { return surf->mapped; });\n}\n\nvoid CSessionLockManager::removeSessionLockSurface(SSessionLockSurface* pSLS) {\n    if (!m_sessionLock)\n        return;\n\n    std::erase_if(m_sessionLock->vSessionLockSurfaces, [&](const auto& other) { return pSLS == other.get(); });\n\n    if (Desktop::focusState()->surface())\n        return;\n\n    for (auto const& sls : m_sessionLock->vSessionLockSurfaces) {\n        if (!sls->mapped)\n            continue;\n\n        Desktop::focusState()->rawSurfaceFocus(sls->surface->surface());\n        break;\n    }\n}\n\nbool CSessionLockManager::clientLocked() {\n    return m_sessionLock && m_sessionLock->hasSentLocked;\n}\n\nbool CSessionLockManager::clientDenied() {\n    return m_sessionLock && m_sessionLock->hasSentDenied;\n}\n\nbool CSessionLockManager::shallConsiderLockMissing() {\n    if (!m_sessionLock)\n        return true;\n\n    static auto LOCKDEAD_SCREEN_DELAY = CConfigValue<Hyprlang::INT>(\"misc:lockdead_screen_delay\");\n\n    return m_sessionLock->lockTimer.getMillis() > *LOCKDEAD_SCREEN_DELAY;\n}\n"
  },
  {
    "path": "src/managers/SessionLockManager.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/time/Timer.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"./eventLoop/EventLoopTimer.hpp\"\n#include <cstdint>\n#include <unordered_map>\n#include <unordered_set>\n\nclass CSessionLockSurface;\nclass CSessionLock;\nclass CWLSurfaceResource;\n\nstruct SSessionLockSurface {\n    SSessionLockSurface(SP<CSessionLockSurface> surface_);\n\n    WP<CSessionLockSurface> surface;\n    WP<CWLSurfaceResource>  pWlrSurface;\n    uint64_t                iMonitorID = -1;\n\n    bool                    mapped = false;\n\n    struct {\n        CHyprSignalListener map;\n        CHyprSignalListener destroy;\n        CHyprSignalListener commit;\n    } listeners;\n};\n\nstruct SSessionLock {\n    WP<CSessionLock>                     lock;\n    CTimer                               lockTimer;\n    SP<CEventLoopTimer>                  sendDeniedTimer;\n\n    std::vector<SP<SSessionLockSurface>> vSessionLockSurfaces;\n\n    struct {\n        CHyprSignalListener newSurface;\n        CHyprSignalListener unlock;\n        CHyprSignalListener destroy;\n    } listeners;\n\n    bool                         hasSentLocked = false;\n    bool                         hasSentDenied = false;\n    std::unordered_set<uint64_t> lockedMonitors;\n};\n\nclass CSessionLockManager {\n  public:\n    CSessionLockManager();\n    ~CSessionLockManager() = default;\n\n    WP<SSessionLockSurface> getSessionLockSurfaceForMonitor(uint64_t);\n\n    bool                    isSessionLocked();\n    bool                    clientLocked();\n    bool                    clientDenied();\n    bool                    isSurfaceSessionLock(SP<CWLSurfaceResource>);\n    bool                    anySessionLockSurfacesPresent();\n\n    void                    removeSessionLockSurface(SSessionLockSurface*);\n\n    void                    onLockscreenRenderedOnMonitor(uint64_t id);\n\n    bool                    shallConsiderLockMissing();\n\n  private:\n    UP<SSessionLock> m_sessionLock;\n\n    struct {\n        CHyprSignalListener newLock;\n    } m_listeners;\n\n    void onNewSessionLock(SP<CSessionLock> pWlrLock);\n    void removeSendDeniedTimer();\n};\n\ninline UP<CSessionLockManager> g_pSessionLockManager;\n"
  },
  {
    "path": "src/managers/TokenManager.cpp",
    "content": "#include \"TokenManager.hpp\"\n#include <uuid/uuid.h>\n#include <algorithm>\n\nCUUIDToken::CUUIDToken(const std::string& uuid_, std::any data_, Time::steady_dur expires) : m_data(data_), m_uuid(uuid_) {\n    m_expiresAt = Time::steadyNow() + expires;\n}\n\nstd::string CUUIDToken::getUUID() {\n    return m_uuid;\n}\n\nstd::string CTokenManager::getRandomUUID() {\n    std::string uuid;\n    do {\n        uuid_t uuid_;\n        uuid_generate_random(uuid_);\n        uuid = std::format(\"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}\", sc<uint16_t>(uuid_[0]), sc<uint16_t>(uuid_[1]),\n                           sc<uint16_t>(uuid_[2]), sc<uint16_t>(uuid_[3]), sc<uint16_t>(uuid_[4]), sc<uint16_t>(uuid_[5]), sc<uint16_t>(uuid_[6]), sc<uint16_t>(uuid_[7]),\n                           sc<uint16_t>(uuid_[8]), sc<uint16_t>(uuid_[9]), sc<uint16_t>(uuid_[10]), sc<uint16_t>(uuid_[11]), sc<uint16_t>(uuid_[12]), sc<uint16_t>(uuid_[13]),\n                           sc<uint16_t>(uuid_[14]), sc<uint16_t>(uuid_[15]));\n    } while (m_tokens.contains(uuid));\n\n    return uuid;\n}\n\nstd::string CTokenManager::registerNewToken(std::any data, Time::steady_dur expires) {\n    std::string uuid = getRandomUUID();\n\n    m_tokens[uuid] = makeShared<CUUIDToken>(uuid, data, expires);\n    return uuid;\n}\n\nSP<CUUIDToken> CTokenManager::getToken(const std::string& uuid) {\n\n    // cleanup expired tokens\n    const auto NOW = Time::steadyNow();\n    std::erase_if(m_tokens, [&NOW](const auto& el) { return el.second->m_expiresAt < NOW; });\n\n    if (!m_tokens.contains(uuid))\n        return {};\n\n    return m_tokens.at(uuid);\n}\n\nvoid CTokenManager::removeToken(SP<CUUIDToken> token) {\n    if (!token)\n        return;\n    m_tokens.erase(token->m_uuid);\n}\n"
  },
  {
    "path": "src/managers/TokenManager.hpp",
    "content": "#pragma once\n\n#include <any>\n#include <unordered_map>\n#include <string>\n\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../helpers/time/Time.hpp\"\n\nclass CUUIDToken {\n  public:\n    CUUIDToken(const std::string& uuid_, std::any data_, Time::steady_dur expires);\n\n    std::string getUUID();\n\n    std::any    m_data;\n\n  private:\n    std::string     m_uuid;\n    Time::steady_tp m_expiresAt;\n\n    friend class CTokenManager;\n};\n\nclass CTokenManager {\n  public:\n    std::string    registerNewToken(std::any data, std::chrono::steady_clock::duration expires);\n    std::string    getRandomUUID();\n\n    SP<CUUIDToken> getToken(const std::string& uuid);\n    void           removeToken(SP<CUUIDToken> token);\n\n  private:\n    std::unordered_map<std::string, SP<CUUIDToken>> m_tokens;\n};\n\ninline UP<CTokenManager> g_pTokenManager;"
  },
  {
    "path": "src/managers/VersionKeeperManager.cpp",
    "content": "#include \"VersionKeeperManager.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../macros.hpp\"\n#include \"../version.h\"\n#include \"../helpers/MiscFunctions.hpp\"\n#include \"../helpers/varlist/VarList.hpp\"\n#include \"eventLoop/EventLoopManager.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../helpers/fs/FsUtils.hpp\"\n\n#include <filesystem>\n#include <fstream>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/os/Process.hpp>\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::OS;\n\nconstexpr const char* VERSION_FILE_NAME = \"lastVersion\";\n\nCVersionKeeperManager::CVersionKeeperManager() {\n    static auto PNONOTIFY = CConfigValue<Hyprlang::INT>(\"ecosystem:no_update_news\");\n\n    const auto  DATAROOT = NFsUtils::getDataHome();\n\n    if (!DATAROOT)\n        return;\n\n    auto LASTVER = NFsUtils::readFileAsString(*DATAROOT + \"/\" + VERSION_FILE_NAME);\n\n    if (!LASTVER) {\n        NFsUtils::writeToFile(*DATAROOT + \"/\" + VERSION_FILE_NAME, \"0.0.0\");\n        LASTVER = \"0.0.0\";\n        return;\n    }\n\n    if (!isMajorVersionOlderThanRunning(*LASTVER)) {\n        Log::logger->log(Log::DEBUG, \"CVersionKeeperManager: Read version {} matches or is older than running major.\", *LASTVER);\n        return;\n    }\n\n    NFsUtils::writeToFile(*DATAROOT + \"/\" + VERSION_FILE_NAME, HYPRLAND_VERSION);\n\n    if (*PNONOTIFY) {\n        Log::logger->log(Log::DEBUG, \"CVersionKeeperManager: updated, but update news is disabled in the config :(\");\n        return;\n    }\n\n    if (!NFsUtils::executableExistsInPath(\"hyprland-update-screen\")) {\n        Log::logger->log(Log::ERR, \"CVersionKeeperManager: hyprland-update-screen doesn't seem to exist, skipping notif about update...\");\n        return;\n    }\n\n    m_fired = true;\n\n    g_pEventLoopManager->doLater([]() {\n        CProcess proc(\"hyprland-update-screen\", {\"--new-version\", HYPRLAND_VERSION});\n        proc.runAsync();\n    });\n}\n\nbool CVersionKeeperManager::isMajorVersionOlderThanRunning(const std::string& ver) {\n    const CVarList        verStrings(ver, 0, '.', true);\n\n    const int             V1 = configStringToInt(verStrings[0]).value_or(0);\n    const int             V2 = configStringToInt(verStrings[1]).value_or(0);\n\n    static const CVarList runningStrings(HYPRLAND_VERSION, 0, '.', true);\n\n    static const int      R1 = configStringToInt(runningStrings[0]).value_or(0);\n    static const int      R2 = configStringToInt(runningStrings[1]).value_or(0);\n\n    if (R1 > V1)\n        return true;\n    if (R2 > V2)\n        return true;\n    return false;\n}\n\nbool CVersionKeeperManager::fired() {\n    return m_fired;\n}\n"
  },
  {
    "path": "src/managers/VersionKeeperManager.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n\nclass CVersionKeeperManager {\n  public:\n    CVersionKeeperManager();\n\n    // whether the update screen was shown this boot.\n    bool fired();\n\n  private:\n    bool isMajorVersionOlderThanRunning(const std::string& ver);\n\n    bool m_fired = false;\n};\n\ninline UP<CVersionKeeperManager> g_pVersionKeeperMgr;"
  },
  {
    "path": "src/managers/WelcomeManager.cpp",
    "content": "#include \"WelcomeManager.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../helpers/fs/FsUtils.hpp\"\n\n#include <hyprutils/os/Process.hpp>\n\nusing namespace Hyprutils::OS;\n\nCWelcomeManager::CWelcomeManager() {\n    static auto PAUTOGEN = CConfigValue<Hyprlang::INT>(\"autogenerated\");\n\n    if (!*PAUTOGEN) {\n        Log::logger->log(Log::DEBUG, \"[welcome] skipping, not autogen\");\n        return;\n    }\n\n    if (g_pCompositor->m_safeMode) {\n        Log::logger->log(Log::DEBUG, \"[welcome] skipping, safe mode\");\n        return;\n    }\n\n    if (!NFsUtils::executableExistsInPath(\"hyprland-welcome\")) {\n        Log::logger->log(Log::DEBUG, \"[welcome] skipping, no welcome app\");\n        return;\n    }\n\n    m_fired = true;\n\n    CProcess welcome(\"hyprland-welcome\", {});\n    welcome.runAsync();\n}\n\nbool CWelcomeManager::fired() {\n    return m_fired;\n}\n"
  },
  {
    "path": "src/managers/WelcomeManager.hpp",
    "content": "#pragma once\n\n#include \"../helpers/memory/Memory.hpp\"\n\nclass CWelcomeManager {\n  public:\n    CWelcomeManager();\n\n    // whether the welcome screen was shown this boot.\n    bool fired();\n\n  private:\n    bool m_fired = false;\n};\n\ninline UP<CWelcomeManager> g_pWelcomeManager;"
  },
  {
    "path": "src/managers/XCursorManager.cpp",
    "content": "#define Time XTime__\nextern \"C\" {\n#include <X11/Xcursor/Xcursor.h>\n}\n#undef Time\n\n#include <algorithm>\n#include <cstring>\n#include <dirent.h>\n#include <filesystem>\n#include <gio/gio.h>\n#include <gio/gsettingsschema.h>\n#include \"config/ConfigValue.hpp\"\n#include \"helpers/CursorShapes.hpp\"\n#include \"../managers/CursorManager.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"XCursorManager.hpp\"\n#include <memory>\n#include <variant>\n#include <fstream>\n\n// clang-format off\nstatic std::vector<uint32_t> HYPR_XCURSOR_PIXELS = {\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x1b001816, 0x01000101, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x8e008173, 0x5f00564d, 0x16001412, 0x09000807, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2b002624, 0x05000404, 0x00000000, 0x35002f2b, 0xd400bead,\n    0xc300b09e, 0x90008275, 0x44003e37, 0x04000403, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x67005a56,\n    0x6f00615c, 0x00000000, 0x00000000, 0x8b007c72, 0xf200d7c6, 0xfa00e0cc, 0xe800d0bd, 0xa0009181, 0x44003e37, 0x1a001815, 0x06000505, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x8d007976, 0xd600b8b3, 0x2500201f, 0x00000000, 0x17001413, 0xbd00a79c, 0xf600dacb, 0xff00e3d1, 0xfc00e1ce, 0xe800d0bc, 0xbf00ac9b,\n    0x95008778, 0x51004a41, 0x0f000e0c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x92007b7b, 0xf500d0cf, 0x9e008685, 0x00000000, 0x00000000, 0x23001f1d, 0x64005853,\n    0x9b008980, 0xd900bfb3, 0xfb00dfce, 0xff00e4d0, 0xfb00e1cd, 0xec00d5c0, 0xa7009788, 0x47004139, 0x1e001b18, 0x05000504, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xa200878a, 0xff00d6d9, 0xd600b4b5,\n    0x0e000c0c, 0x00000000, 0x00000000, 0x02000202, 0x0c000b0a, 0x30002a28, 0x8e007d75, 0xd600bdb0, 0xef00d4c4, 0xfb00e0ce, 0xff00e4d0, 0xe600cfbb, 0xb800a695, 0x5f00564d,\n    0x06000505, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x02000202, 0xc600a3aa, 0xff00d3da, 0xea00c3c8, 0x08000707, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01000101, 0x2a002523, 0x61005550, 0x9500837b,\n    0xd800bfb1, 0xfd00e1cf, 0xff00e5d0, 0xf500dcc7, 0x7c007065, 0x2a002622, 0x01000101, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x06000505, 0xd600aeb9, 0xff00d0dc, 0xcc00a7af, 0x04000303, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x01000101, 0x01000101, 0x2c002724, 0xa1008e85, 0xe600ccbd, 0xf800ddcb, 0xef00d6c3, 0xc300af9f, 0x2c002824, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x09000708, 0xd800adbc, 0xff00cdde, 0xb90095a0, 0x02000202, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10000e0d, 0x4b00423e, 0xa4009088, 0xfd00dfd0, 0xff00e3d1,\n    0xae009c8f, 0x42003b36, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x14001012, 0xf400c0d6,\n    0xff00cadf, 0xb2008e9c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x02000202, 0x1200100f, 0xa2008e86, 0xec00cfc3, 0xfc00ded0, 0xc300ada0, 0x15001311, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x2e002429, 0xfd00c4e0, 0xff00c7e2, 0x8f00707e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x1e001a19, 0x75006662, 0xfb00dbd1, 0xf700d9cc, 0x9600847c, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e002f37, 0xfc00c1e1, 0xff00c5e3, 0x60004b55, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x15001212, 0xa6008f8b, 0xff00ddd5,\n    0xf800d8ce, 0x36002f2d, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x51003d49, 0xfe00bfe5, 0xfe00c1e4, 0x4c003a44,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x01000101, 0x1d001918, 0xf400d1cd, 0xfe00dad5, 0xb3009a96, 0x03000303, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x66004b5d, 0xff00bee7, 0xfd00bee5, 0x4500343f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x02000202, 0x82006e6e, 0xff00d8d7, 0xd800b9b6, 0x33002c2b, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x70005267, 0xff00bbe9, 0xfa00b8e3, 0x29001e25, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f003536, 0xea00c3c7, 0xf800d1d3,\n    0x4a003e3f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5f004458, 0xff00b8eb, 0xf400b1e0, 0x29001e26, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x1b001617, 0xe100bac0, 0xff00d4da, 0x82006c6f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5a004054, 0xfe00b4eb,\n    0xfb00b3e8, 0x3b002a36, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0a000809, 0xc900a4ad, 0xff00d1dc, 0x88007075, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x44002f3f, 0xf300aae3, 0xfc00b1ea, 0x48003343, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x05000404, 0xdf00b3c2, 0xff00cedd, 0x8f00747c, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x25001a23, 0xf200a6e4, 0xff00b1ef, 0x84005c7c, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x09000708,\n    0xe400b5c8, 0xff00cbdf, 0x78006068, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x12000c11, 0xc00082b6, 0xff00aef1, 0xaa0075a0,\n    0x11000c10, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x1e00171b, 0xf700c1db, 0xff00c8e1, 0x4b003b42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x05000305, 0x8d005f86, 0xfc00aaef, 0xed00a0e1, 0x26001a24, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x6f005463, 0xff00c4e3, 0xf500beda, 0x0c00090b, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e00293b, 0xed009de2, 0xff00aaf3, 0x8b005d84, 0x04000304, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x07000506, 0xeb00b1d4, 0xff00c1e6, 0xba008ea7,\n    0x02000202, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xb20075ab, 0xff00a7f4, 0xf300a1e9, 0x35002333,\n    0x06000406, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x8900647d, 0xff00bde8, 0xfb00bce2, 0x26001d22, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x2b001c29, 0xf1009ce8, 0xfe00a5f4, 0xc60082be, 0x3b002738, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x01000101, 0x54003c4d, 0xfd00b8e8, 0xff00baea, 0x96006f89, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x78004d74, 0xe20091da, 0xff00a5f6, 0xb60077af, 0x42002b3f, 0x0c00080c, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x9400688a, 0xff00b5ed, 0xff00b6ec, 0xcc0093bc, 0x02000102, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000304, 0x8100527d, 0xeb0096e4, 0xf900a0f1, 0xdc008dd4,\n    0x9b006595, 0x32002130, 0x0a00070a, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2100161f, 0x5300394e, 0xe2009cd4, 0xff00b1ef, 0xff00b2ee, 0xc8008cba, 0x17001015,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x19001018, 0x5e003b5b, 0xcb0081c5, 0xff00a2f8, 0xfc00a1f4, 0xde0090d6, 0xa9006da3, 0x8300567e, 0x6f00496a, 0x76004e71, 0xbb007db2, 0xeb009edf, 0xfb00a9ee, 0xff00adf1,\n    0xfd00adee, 0x9e006d95, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000304, 0x34002133, 0xa60069a1, 0xd70089d1, 0xf2009bea, 0xff00a3f7, 0xfb00a1f2, 0xfb00a2f2, 0xff00a6f5,\n    0xff00a8f4, 0xfd00a7f2, 0xed009ee2, 0xcf008bc5, 0x14000e13, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x11000b11, 0x35002134, 0x5b003959,\n    0x8b005887, 0xb30072ae, 0xc90080c3, 0xd10086ca, 0xa6006ba0, 0x65004261, 0x3c002839, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x01000101, 0x06000406, 0x0d00080d, 0x0f000a0f, 0x0a00060a, 0x01000101, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,\n    0x00000000, 0x00000000};\n// clang-format on\n\nCXCursorManager::CXCursorManager() {\n    m_hyprCursor = makeShared<SXCursors>();\n    SXCursorImage image;\n    image.size    = {32, 32};\n    image.hotspot = {3, 2};\n    image.pixels  = HYPR_XCURSOR_PIXELS;\n    image.delay   = 0;\n\n    m_hyprCursor->images.push_back(image);\n    m_hyprCursor->shape = \"left_ptr\";\n    m_defaultCursor     = m_hyprCursor;\n}\n\nvoid CXCursorManager::loadTheme(std::string const& name, int size, float scale) {\n    if (m_lastLoadSize == (size * std::ceil(scale)) && m_themeName == name && m_lastLoadScale == scale)\n        return;\n\n    m_lastLoadSize  = size * std::ceil(scale);\n    m_lastLoadScale = scale;\n    m_themeName     = name.empty() ? \"default\" : name;\n    m_defaultCursor.reset();\n    m_cursors.clear();\n\n    auto paths = themePaths(m_themeName);\n    if (paths.empty()) {\n        Log::logger->log(Log::ERR, \"XCursor librarypath is empty loading standard XCursors\");\n        m_cursors = loadStandardCursors(m_themeName, m_lastLoadSize);\n    } else {\n        for (auto const& p : paths) {\n            try {\n                auto dirCursors = loadAllFromDir(p, m_lastLoadSize);\n                std::ranges::copy_if(dirCursors, std::back_inserter(m_cursors),\n                                     [this](auto const& p) { return std::ranges::none_of(m_cursors, [&p](auto const& dp) { return dp->shape == p->shape; }); });\n            } catch (std::exception& e) { Log::logger->log(Log::ERR, \"XCursor path {} can't be loaded: threw error {}\", p, e.what()); }\n        }\n    }\n\n    if (m_cursors.empty()) {\n        Log::logger->log(Log::ERR, \"XCursor failed finding any shapes in theme \\\"{}\\\".\", m_themeName);\n        m_defaultCursor = m_hyprCursor;\n        return;\n    }\n\n    for (auto const& shape : CURSOR_SHAPE_NAMES) {\n        auto legacyName = getLegacyShapeName(shape);\n        if (legacyName.empty())\n            continue;\n\n        auto it = std::ranges::find_if(m_cursors, [&legacyName](auto const& c) { return c->shape == legacyName; });\n\n        if (it == m_cursors.end()) {\n            Log::logger->log(Log::DEBUG, \"XCursor failed to find a legacy shape with name {}, skipping\", legacyName);\n            continue;\n        }\n\n        if (std::ranges::any_of(m_cursors, [&shape](auto const& dp) { return dp->shape == shape; })) {\n            Log::logger->log(Log::DEBUG, \"XCursor already has a shape {} loaded, skipping\", shape);\n            continue;\n        }\n\n        auto cursor    = makeShared<SXCursors>();\n        cursor->images = it->get()->images;\n        cursor->shape  = shape;\n\n        m_cursors.emplace_back(cursor);\n    }\n\n    syncGsettings();\n}\n\nSP<SXCursors> CXCursorManager::getShape(std::string const& shape, int size, float scale) {\n    // monitor scaling changed etc, so reload theme with new size.\n    if ((size * std::ceil(scale)) != m_lastLoadSize || scale != m_lastLoadScale)\n        loadTheme(m_themeName, size, scale);\n\n    // try to get an icon we know if we have one\n    for (auto const& c : m_cursors) {\n        if (c->shape != shape)\n            continue;\n\n        return c;\n    }\n\n    Log::logger->log(Log::WARN, \"XCursor couldn't find shape {} , using default cursor instead\", shape);\n    return m_defaultCursor;\n}\n\nSP<SXCursors> CXCursorManager::createCursor(std::string const& shape, void* ximages) {\n    auto           xcursor = makeShared<SXCursors>();\n    XcursorImages* xImages = sc<XcursorImages*>(ximages);\n\n    for (int i = 0; i < xImages->nimage; i++) {\n        auto          xImage = xImages->images[i];\n        SXCursorImage image;\n        image.size    = {sc<int>(xImage->width), sc<int>(xImage->height)};\n        image.hotspot = {sc<int>(xImage->xhot), sc<int>(xImage->yhot)};\n        image.pixels.resize(sc<size_t>(xImage->width) * xImage->height);\n        std::memcpy(image.pixels.data(), xImage->pixels, sc<size_t>(xImage->width) * xImage->height * sizeof(uint32_t));\n        image.delay = xImage->delay;\n\n        xcursor->images.emplace_back(image);\n    }\n\n    xcursor->shape = shape;\n\n    return xcursor;\n}\n\nstd::set<std::string> CXCursorManager::themePaths(std::string const& theme) {\n    auto const* path = XcursorLibraryPath();\n\n    auto        expandTilde = [](std::string const& path) {\n        if (!path.empty() && path[0] == '~') {\n            const char* home = std::getenv(\"HOME\");\n            if (home)\n                return std::string(home) + path.substr(1);\n        }\n        return path;\n    };\n\n    auto getInheritThemes = [](std::string const& indexTheme) {\n        std::ifstream            infile(indexTheme);\n        std::string              line;\n        std::vector<std::string> themes;\n\n        Log::logger->log(Log::DEBUG, \"XCursor parsing index.theme {}\", indexTheme);\n\n        while (std::getline(infile, line)) {\n            if (line.empty())\n                continue;\n\n            // Trim leading and trailing whitespace\n            auto pos = line.find_first_not_of(\" \\t\\n\\r\");\n            if (pos != std::string::npos)\n                line.erase(0, pos);\n\n            pos = line.find_last_not_of(\" \\t\\n\\r\");\n            if (pos != std::string::npos && pos < line.length()) {\n                line.erase(pos + 1);\n            }\n\n            if (line.rfind(\"Inherits\", 8) != std::string::npos) { // Check if line starts with \"Inherits\"\n                std::string inheritThemes = line.substr(8);       // Extract the part after \"Inherits\"\n                if (inheritThemes.empty())\n                    continue;\n\n                // Remove leading whitespace from inheritThemes and =\n                pos = inheritThemes.find_first_not_of(\" \\t\\n\\r\");\n                if (pos != std::string::npos)\n                    inheritThemes.erase(0, pos);\n\n                if (inheritThemes.empty())\n                    continue;\n\n                if (inheritThemes.at(0) == '=')\n                    inheritThemes.erase(0, 1);\n                else\n                    continue; // not correct formatted index.theme\n\n                pos = inheritThemes.find_first_not_of(\" \\t\\n\\r\");\n                if (pos != std::string::npos)\n                    inheritThemes.erase(0, pos);\n\n                std::stringstream inheritStream(inheritThemes);\n                std::string       inheritTheme;\n                while (std::getline(inheritStream, inheritTheme, ',')) {\n                    if (inheritTheme.empty())\n                        continue;\n\n                    // Trim leading and trailing whitespace from each theme\n                    pos = inheritTheme.find_first_not_of(\" \\t\\n\\r\");\n                    if (pos != std::string::npos)\n                        inheritTheme.erase(0, pos);\n\n                    pos = inheritTheme.find_last_not_of(\" \\t\\n\\r\");\n                    if (pos != std::string::npos && pos < inheritTheme.length())\n                        inheritTheme.erase(inheritTheme.find_last_not_of(\" \\t\\n\\r\") + 1);\n\n                    themes.push_back(inheritTheme);\n                }\n            }\n        }\n        infile.close();\n\n        return themes;\n    };\n\n    std::set<std::string> paths;\n    std::set<std::string> inherits;\n\n    auto                  scanTheme = [&path, &paths, &expandTilde, &inherits, &getInheritThemes](auto const& t) {\n        std::stringstream ss(path);\n        std::string       line;\n\n        Log::logger->log(Log::DEBUG, \"XCursor scanning theme {}\", t);\n\n        while (std::getline(ss, line, ':')) {\n            auto p = expandTilde(line + \"/\" + t + \"/cursors\");\n            if (std::filesystem::exists(p) && std::filesystem::is_directory(p)) {\n                Log::logger->log(Log::DEBUG, \"XCursor using theme path {}\", p);\n                paths.insert(p);\n            }\n\n            auto inherit = expandTilde(line + \"/\" + t + \"/index.theme\");\n            if (std::filesystem::exists(inherit) && std::filesystem::is_regular_file(inherit)) {\n                auto inheritThemes = getInheritThemes(inherit);\n                for (auto const& i : inheritThemes) {\n                    Log::logger->log(Log::DEBUG, \"XCursor theme {} inherits {}\", t, i);\n                    inherits.insert(i);\n                }\n            }\n        }\n    };\n\n    if (path) {\n        scanTheme(theme);\n        while (!inherits.empty()) {\n            auto oldInherits = inherits;\n            for (auto const& i : oldInherits)\n                scanTheme(i);\n\n            if (oldInherits.size() == inherits.size())\n                break;\n        }\n    }\n\n    return paths;\n}\n\nstd::string CXCursorManager::getLegacyShapeName(std::string const& shape) {\n    if (shape == \"invalid\")\n        return std::string();\n    else if (shape == \"default\")\n        return \"left_ptr\";\n    else if (shape == \"context-menu\")\n        return \"left_ptr\";\n    else if (shape == \"help\")\n        return \"left_ptr\";\n    else if (shape == \"pointer\")\n        return \"hand2\";\n    else if (shape == \"progress\")\n        return \"watch\";\n    else if (shape == \"wait\")\n        return \"watch\";\n    else if (shape == \"cell\")\n        return \"plus\";\n    else if (shape == \"crosshair\")\n        return \"cross\";\n    else if (shape == \"text\")\n        return \"xterm\";\n    else if (shape == \"vertical-text\")\n        return \"xterm\";\n    else if (shape == \"alias\")\n        return \"dnd-link\";\n    else if (shape == \"copy\")\n        return \"dnd-copy\";\n    else if (shape == \"move\")\n        return \"dnd-move\";\n    else if (shape == \"no-drop\")\n        return \"dnd-none\";\n    else if (shape == \"not-allowed\")\n        return \"crossed_circle\";\n    else if (shape == \"grab\")\n        return \"hand1\";\n    else if (shape == \"grabbing\")\n        return \"hand1\";\n    else if (shape == \"e-resize\")\n        return \"right_side\";\n    else if (shape == \"n-resize\")\n        return \"top_side\";\n    else if (shape == \"ne-resize\")\n        return \"top_right_corner\";\n    else if (shape == \"nw-resize\")\n        return \"top_left_corner\";\n    else if (shape == \"s-resize\")\n        return \"bottom_side\";\n    else if (shape == \"se-resize\")\n        return \"bottom_right_corner\";\n    else if (shape == \"sw-resize\")\n        return \"bottom_left_corner\";\n    else if (shape == \"w-resize\")\n        return \"left_side\";\n    else if (shape == \"ew-resize\")\n        return \"sb_h_double_arrow\";\n    else if (shape == \"ns-resize\")\n        return \"sb_v_double_arrow\";\n    else if (shape == \"nesw-resize\")\n        return \"fd_double_arrow\";\n    else if (shape == \"nwse-resize\")\n        return \"bd_double_arrow\";\n    else if (shape == \"col-resize\")\n        return \"sb_h_double_arrow\";\n    else if (shape == \"row-resize\")\n        return \"sb_v_double_arrow\";\n    else if (shape == \"all-scroll\")\n        return \"fleur\";\n    else if (shape == \"zoom-in\")\n        return \"left_ptr\";\n    else if (shape == \"zoom-out\")\n        return \"left_ptr\";\n    else if (shape == \"dnd-ask\")\n        return \"dnd-copy\";\n    else if (shape == \"all-resize\")\n        return \"fleur\";\n\n    return std::string();\n};\n\n// Taken from https://gitlab.freedesktop.org/xorg/lib/libxcursor/-/blob/master/src/library.c\n// clang-format off\nstatic std::array<const char*, 77> XCURSOR_STANDARD_NAMES = {\n    \"X_cursor\",\n    \"arrow\",\n    \"based_arrow_down\",\n    \"based_arrow_up\",\n    \"boat\",\n    \"bogosity\",\n    \"bottom_left_corner\",\n    \"bottom_right_corner\",\n    \"bottom_side\",\n    \"bottom_tee\",\n    \"box_spiral\",\n    \"center_ptr\",\n    \"circle\",\n    \"clock\",\n    \"coffee_mug\",\n    \"cross\",\n    \"cross_reverse\",\n    \"crosshair\",\n    \"diamond_cross\",\n    \"dot\",\n    \"dotbox\",\n    \"double_arrow\",\n    \"draft_large\",\n    \"draft_small\",\n    \"draped_box\",\n    \"exchange\",\n    \"fleur\",\n    \"gobbler\",\n    \"gumby\",\n    \"hand1\",\n    \"hand2\",\n    \"heart\",\n    \"icon\",\n    \"iron_cross\",\n    \"left_ptr\",\n    \"left_side\",\n    \"left_tee\",\n    \"leftbutton\",\n    \"ll_angle\",\n    \"lr_angle\",\n    \"man\",\n    \"middlebutton\",\n    \"mouse\",\n    \"pencil\",\n    \"pirate\",\n    \"plus\",\n    \"question_arrow\",\n    \"right_ptr\",\n    \"right_side\",\n    \"right_tee\",\n    \"rightbutton\",\n    \"rtl_logo\",\n    \"sailboat\",\n    \"sb_down_arrow\",\n    \"sb_h_double_arrow\",\n    \"sb_left_arrow\",\n    \"sb_right_arrow\",\n    \"sb_up_arrow\",\n    \"sb_v_double_arrow\",\n    \"shuttle\",\n    \"sizing\",\n    \"spider\",\n    \"spraycan\",\n    \"star\",\n    \"target\",\n    \"tcross\",\n    \"top_left_arrow\",\n    \"top_left_corner\",\n    \"top_right_corner\",\n    \"top_side\",\n    \"top_tee\",\n    \"trek\",\n    \"ul_angle\",\n    \"umbrella\",\n    \"ur_angle\",\n    \"watch\",\n    \"xterm\",\n};\n// clang-format on\n\nstd::vector<SP<SXCursors>> CXCursorManager::loadStandardCursors(std::string const& name, int size) {\n    std::vector<SP<SXCursors>> newCursors;\n\n    // load the default xcursor shapes that exist in the theme\n    for (size_t i = 0; i < XCURSOR_STANDARD_NAMES.size(); ++i) {\n        std::string shape{XCURSOR_STANDARD_NAMES[i]};\n        auto        xImages = XcursorShapeLoadImages(i << 1 /* wtf xcursor? */, name.c_str(), size);\n\n        if (!xImages) {\n            Log::logger->log(Log::WARN, \"XCursor failed to find a shape with name {}, trying size 24.\", shape);\n            xImages = XcursorShapeLoadImages(i << 1 /* wtf xcursor? */, name.c_str(), 24);\n\n            if (!xImages) {\n                Log::logger->log(Log::WARN, \"XCursor failed to find a shape with name {}, skipping\", shape);\n                continue;\n            }\n        }\n\n        auto cursor = createCursor(shape, xImages);\n        newCursors.emplace_back(cursor);\n\n        if (!m_defaultCursor && (shape == \"left_ptr\" || shape == \"arrow\"))\n            m_defaultCursor = cursor;\n\n        XcursorImagesDestroy(xImages);\n    }\n\n    // broken theme.. just set it.\n    if (!newCursors.empty() && !m_defaultCursor)\n        m_defaultCursor = newCursors.front();\n\n    return newCursors;\n}\n\nstd::vector<SP<SXCursors>> CXCursorManager::loadAllFromDir(std::string const& path, int size) {\n    std::vector<SP<SXCursors>> newCursors;\n\n    if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) {\n        for (const auto& entry : std::filesystem::directory_iterator(path)) {\n            std::error_code e1, e2;\n            if ((!entry.is_regular_file(e1) && !entry.is_symlink(e2)) || e1 || e2) {\n                Log::logger->log(Log::WARN, \"XCursor failed to load shape {}: {}\", entry.path().stem().string(), e1 ? e1.message() : e2.message());\n                continue;\n            }\n\n            auto const& full = entry.path().string();\n            using PcloseType = int (*)(FILE*);\n            const std::unique_ptr<FILE, PcloseType> f(fopen(full.c_str(), \"r\"), fclose);\n\n            if (!f)\n                continue;\n\n            auto xImages = XcursorFileLoadImages(f.get(), size);\n\n            if (!xImages) {\n                Log::logger->log(Log::WARN, \"XCursor failed to load image {}, trying size 24.\", full);\n                xImages = XcursorFileLoadImages(f.get(), 24);\n\n                if (!xImages) {\n                    Log::logger->log(Log::WARN, \"XCursor failed to load image {}, skipping\", full);\n                    continue;\n                }\n            }\n\n            auto const& shape  = entry.path().filename().string();\n            auto        cursor = createCursor(shape, xImages);\n            newCursors.emplace_back(cursor);\n\n            if (!m_defaultCursor && (shape == \"left_ptr\" || shape == \"arrow\"))\n                m_defaultCursor = cursor;\n\n            XcursorImagesDestroy(xImages);\n        }\n    }\n\n    // broken theme.. just set it.\n    if (!newCursors.empty() && !m_defaultCursor)\n        m_defaultCursor = newCursors.front();\n\n    return newCursors;\n}\n\nvoid CXCursorManager::syncGsettings() {\n    static auto SYNCGSETTINGS = CConfigValue<Hyprlang::INT>(\"cursor:sync_gsettings_theme\");\n    if (!*SYNCGSETTINGS)\n        return;\n\n    auto checkParamExists = [](std::string const& paramName, std::string const& category) {\n        auto* gSettingsSchemaSource = g_settings_schema_source_get_default();\n\n        if (!gSettingsSchemaSource) {\n            Log::logger->log(Log::WARN, \"GSettings default schema source does not exist, can't sync GSettings\");\n            return false;\n        }\n\n        auto* gSettingsSchema = g_settings_schema_source_lookup(gSettingsSchemaSource, category.c_str(), true);\n        bool  hasParam        = false;\n\n        if (gSettingsSchema) {\n            hasParam = gSettingsSchema && g_settings_schema_has_key(gSettingsSchema, paramName.c_str());\n            g_settings_schema_unref(gSettingsSchema);\n        }\n\n        return hasParam;\n    };\n\n    using SettingValue = std::variant<std::string, int>;\n    auto setValue      = [&checkParamExists](std::string const& paramName, const SettingValue& paramValue, std::string const& category) {\n        if (!checkParamExists(paramName, category)) {\n            Log::logger->log(Log::WARN, \"GSettings parameter doesn't exist {} in {}\", paramName, category);\n            return;\n        }\n\n        auto* gsettings = g_settings_new(category.c_str());\n\n        if (!gsettings) {\n            Log::logger->log(Log::WARN, \"GSettings failed to allocate new settings with category {}\", category);\n            return;\n        }\n\n        std::visit(\n            [&](auto&& value) {\n                using T = std::decay_t<decltype(value)>;\n                if constexpr (std::is_same_v<T, std::string>)\n                    g_settings_set_string(gsettings, paramName.c_str(), value.c_str());\n                else if constexpr (std::is_same_v<T, int>)\n                    g_settings_set_int(gsettings, paramName.c_str(), value);\n            },\n            paramValue);\n\n        g_settings_sync();\n        g_object_unref(gsettings);\n    };\n\n    int unscaledSize = m_lastLoadSize / std::ceil(m_lastLoadScale);\n    setValue(\"cursor-theme\", m_themeName, \"org.gnome.desktop.interface\");\n    setValue(\"cursor-size\", unscaledSize, \"org.gnome.desktop.interface\");\n}\n"
  },
  {
    "path": "src/managers/XCursorManager.hpp",
    "content": "#pragma once\n#include <string>\n#include <vector>\n#include <set>\n#include <array>\n#include <cstdint>\n#include <hyprutils/math/Vector2D.hpp>\n#include \"helpers/memory/Memory.hpp\"\n\n// gangsta bootleg XCursor impl. adidas balkanized\nstruct SXCursorImage {\n    Hyprutils::Math::Vector2D size;\n    Hyprutils::Math::Vector2D hotspot;\n    std::vector<uint32_t>     pixels; // XPixel is a u32\n    uint32_t                  delay;  // animation delay to next frame (ms)\n};\n\nstruct SXCursors {\n    std::vector<SXCursorImage> images;\n    std::string                shape;\n};\n\nclass CXCursorManager {\n  public:\n    CXCursorManager();\n    ~CXCursorManager() = default;\n\n    void          loadTheme(const std::string& name, int size, float scale);\n    SP<SXCursors> getShape(std::string const& shape, int size, float scale);\n    void          syncGsettings();\n\n  private:\n    SP<SXCursors>              createCursor(std::string const& shape, void* /* XcursorImages* */ xImages);\n    std::set<std::string>      themePaths(std::string const& theme);\n    std::string                getLegacyShapeName(std::string const& shape);\n    std::vector<SP<SXCursors>> loadStandardCursors(std::string const& name, int size);\n    std::vector<SP<SXCursors>> loadAllFromDir(std::string const& path, int size);\n\n    int                        m_lastLoadSize  = 0;\n    float                      m_lastLoadScale = 0;\n    std::string                m_themeName     = \"\";\n    SP<SXCursors>              m_defaultCursor;\n    SP<SXCursors>              m_hyprCursor;\n    std::vector<SP<SXCursors>> m_cursors;\n};\n"
  },
  {
    "path": "src/managers/XWaylandManager.cpp",
    "content": "#include \"XWaylandManager.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../protocols/XDGShell.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../xwayland/XWayland.hpp\"\n#include <hyprutils/math/Vector2D.hpp>\n\n#define OUTPUT_MANAGER_VERSION                   3\n#define OUTPUT_DONE_DEPRECATED_SINCE_VERSION     3\n#define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3\n\nCHyprXWaylandManager::CHyprXWaylandManager() = default;\n\nCHyprXWaylandManager::~CHyprXWaylandManager() {\n#ifndef NO_XWAYLAND\n    unsetenv(\"DISPLAY\");\n#endif\n}\n\nSP<CWLSurfaceResource> CHyprXWaylandManager::getWindowSurface(PHLWINDOW pWindow) {\n    return pWindow ? pWindow->wlSurface()->resource() : nullptr;\n}\n\nvoid CHyprXWaylandManager::activateSurface(SP<CWLSurfaceResource> pSurface, bool activate) {\n    if (!pSurface)\n        return;\n\n    auto HLSurface = Desktop::View::CWLSurface::fromResource(pSurface);\n    if (!HLSurface) {\n        Log::logger->log(Log::TRACE, \"CHyprXWaylandManager::activateSurface on non-desktop surface, ignoring\");\n        return;\n    }\n\n    const auto PWINDOW = Desktop::View::CWindow::fromView(HLSurface->view());\n    if (!PWINDOW) {\n        Log::logger->log(Log::TRACE, \"CHyprXWaylandManager::activateSurface on non-window surface, ignoring\");\n        return;\n    }\n\n    if (PWINDOW->m_isX11) {\n        if (PWINDOW->m_xwaylandSurface) {\n            if (activate) {\n                PWINDOW->m_xwaylandSurface->setMinimized(false);\n                PWINDOW->m_xwaylandSurface->restackToTop();\n            }\n            PWINDOW->m_xwaylandSurface->activate(activate);\n        }\n    } else if (PWINDOW->m_xdgSurface && PWINDOW->m_xdgSurface->m_toplevel)\n        PWINDOW->m_xdgSurface->m_toplevel->setActive(activate);\n}\n\nvoid CHyprXWaylandManager::activateWindow(PHLWINDOW pWindow, bool activate) {\n    if (pWindow->m_isX11) {\n\n        if (activate) {\n            pWindow->sendWindowSize(true); // update xwayland output pos\n            pWindow->m_xwaylandSurface->setMinimized(false);\n\n            if (!pWindow->isX11OverrideRedirect())\n                pWindow->m_xwaylandSurface->restackToTop();\n        }\n\n        pWindow->m_xwaylandSurface->activate(activate);\n\n    } else if (pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel)\n        pWindow->m_xdgSurface->m_toplevel->setActive(activate);\n\n    if (activate) {\n        Desktop::focusState()->surface() = getWindowSurface(pWindow);\n        Desktop::focusState()->window()  = pWindow;\n    }\n\n    if (!pWindow->m_pinned)\n        pWindow->m_workspace->m_lastFocusedWindow = pWindow;\n}\n\nCBox CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow) {\n    if (!pWindow)\n        return {};\n\n    CBox box;\n\n    if (pWindow->m_isX11)\n        box = pWindow->m_xwaylandSurface->m_geometry;\n    else if (pWindow->m_xdgSurface)\n        box = pWindow->m_xdgSurface->m_current.geometry;\n\n    Vector2D MINSIZE = pWindow->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});\n    Vector2D MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX).clamp(MINSIZE + Vector2D{1, 1});\n\n    Vector2D oldSize = box.size();\n    box.w            = std::clamp(box.w, MINSIZE.x, MAXSIZE.x);\n    box.h            = std::clamp(box.h, MINSIZE.y, MAXSIZE.y);\n    box.translate((oldSize - box.size()) / 2.F);\n\n    return box;\n}\n\nvoid CHyprXWaylandManager::sendCloseWindow(PHLWINDOW pWindow) {\n    if (pWindow->m_isX11)\n        pWindow->m_xwaylandSurface->close();\n    else if (pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel)\n        pWindow->m_xdgSurface->m_toplevel->close();\n}\n\nbool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) {\n    if (pWindow->m_isX11) {\n        for (const auto& a : pWindow->m_xwaylandSurface->m_atoms)\n            if (a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DIALOG\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_SPLASH\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_TOOLBAR\"] ||\n                a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_UTILITY\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_TOOLTIP\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_POPUP_MENU\"] ||\n                a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DOCK\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_MENU\"] ||\n                a == HYPRATOMS[\"_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\"]) {\n\n                if (a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_MENU\"])\n                    pWindow->m_X11ShouldntFocus = true;\n\n                if (a != HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DIALOG\"])\n                    pWindow->m_noInitialFocus = true;\n\n                return true;\n            }\n\n        if (pWindow->isModal() || pWindow->m_xwaylandSurface->m_transient ||\n            (pWindow->m_xwaylandSurface->m_role.contains(\"task_dialog\") || pWindow->m_xwaylandSurface->m_role.contains(\"pop-up\")) || pWindow->m_xwaylandSurface->m_overrideRedirect)\n            return true;\n\n        const auto SIZEHINTS = pWindow->m_xwaylandSurface->m_sizeHints.get();\n        if (pWindow->m_xwaylandSurface->m_transient || pWindow->m_xwaylandSurface->m_parent ||\n            (SIZEHINTS && SIZEHINTS->min_width > 0 && SIZEHINTS->min_height > 0 && SIZEHINTS->max_width > 0 && SIZEHINTS->max_height > 0 &&\n             (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height)))\n            return true;\n    } else {\n        if (!pWindow->m_xdgSurface || !pWindow->m_xdgSurface->m_toplevel)\n            return false;\n\n        const auto PSTATE = pending ? &pWindow->m_xdgSurface->m_toplevel->m_pending : &pWindow->m_xdgSurface->m_toplevel->m_current;\n        if (pWindow->m_xdgSurface->m_toplevel->m_parent ||\n            (PSTATE->minSize.x != 0 && PSTATE->minSize.y != 0 && (PSTATE->minSize.x == PSTATE->maxSize.x || PSTATE->minSize.y == PSTATE->maxSize.y)))\n            return true;\n    }\n\n    return false;\n}\n\nvoid CHyprXWaylandManager::checkBorders(PHLWINDOW pWindow) {\n    if (!pWindow->m_isX11)\n        return;\n\n    for (auto const& a : pWindow->m_xwaylandSurface->m_atoms) {\n        if (a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_POPUP_MENU\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_NOTIFICATION\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\"] ||\n            a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_COMBO\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_MENU\"] || a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_SPLASH\"] ||\n            a == HYPRATOMS[\"_NET_WM_WINDOW_TYPE_TOOLTIP\"]) {\n\n            pWindow->m_X11DoesntWantBorders = true;\n            return;\n        }\n    }\n\n    if (pWindow->isX11OverrideRedirect())\n        pWindow->m_X11DoesntWantBorders = true;\n}\n\nvoid CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscreen) {\n    if (!pWindow)\n        return;\n\n    if (pWindow->m_isX11)\n        pWindow->m_xwaylandSurface->setFullscreen(fullscreen);\n    else if (pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel)\n        pWindow->m_xdgSurface->m_toplevel->setFullscreen(fullscreen);\n}\n\nVector2D CHyprXWaylandManager::waylandToXWaylandCoords(const Vector2D& coord) {\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    PHLMONITOR  pMonitor     = nullptr;\n    double      bestDistance = __FLT_MAX__;\n    for (const auto& m : g_pCompositor->m_monitors) {\n        const auto SIZ = *PXWLFORCESCALEZERO ? m->m_transformedSize : m->m_size;\n\n        double     distance = vecToRectDistanceSquared(coord, {m->m_position.x, m->m_position.y}, {m->m_position.x + SIZ.x - 1, m->m_position.y + SIZ.y - 1});\n\n        if (distance < bestDistance) {\n            bestDistance = distance;\n            pMonitor     = m;\n        }\n    }\n\n    if (!pMonitor)\n        return Vector2D{};\n\n    // get local coords\n    Vector2D result = coord - pMonitor->m_position;\n    // if scaled, scale\n    if (*PXWLFORCESCALEZERO)\n        result *= pMonitor->m_scale;\n    // add pos\n    result += pMonitor->m_xwaylandPosition;\n\n    return result;\n}\n\nVector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) {\n\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    PHLMONITOR  pMonitor     = nullptr;\n    double      bestDistance = __FLT_MAX__;\n    for (const auto& m : g_pCompositor->m_monitors) {\n        const auto SIZ = *PXWLFORCESCALEZERO ? m->m_transformedSize : m->m_size;\n\n        double     distance =\n            vecToRectDistanceSquared(coord, {m->m_xwaylandPosition.x, m->m_xwaylandPosition.y}, {m->m_xwaylandPosition.x + SIZ.x - 1, m->m_xwaylandPosition.y + SIZ.y - 1});\n\n        if (distance < bestDistance) {\n            bestDistance = distance;\n            pMonitor     = m;\n        }\n    }\n\n    if (!pMonitor)\n        return Vector2D{};\n\n    // get local coords\n    Vector2D result = coord - pMonitor->m_xwaylandPosition;\n    // if scaled, unscale\n    if (*PXWLFORCESCALEZERO)\n        result /= pMonitor->m_scale;\n    // add pos\n    result += pMonitor->m_position;\n\n    return result;\n}\n"
  },
  {
    "path": "src/managers/XWaylandManager.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include <optional>\n\nclass CWLSurfaceResource;\n\nclass CHyprXWaylandManager {\n  public:\n    CHyprXWaylandManager();\n    ~CHyprXWaylandManager();\n\n    SP<CWLSurfaceResource> getWindowSurface(PHLWINDOW);\n    void                   activateSurface(SP<CWLSurfaceResource>, bool);\n    void                   activateWindow(PHLWINDOW, bool);\n    CBox                   getGeometryForWindow(PHLWINDOW);\n    void                   sendCloseWindow(PHLWINDOW);\n    void                   setWindowFullscreen(PHLWINDOW, bool);\n    bool                   shouldBeFloated(PHLWINDOW, bool pending = false);\n    void                   checkBorders(PHLWINDOW);\n    Vector2D               xwaylandToWaylandCoords(const Vector2D&);\n    Vector2D               waylandToXWaylandCoords(const Vector2D&);\n};\n\ninline UP<CHyprXWaylandManager> g_pXWaylandManager;"
  },
  {
    "path": "src/managers/animation/AnimationManager.cpp",
    "content": "#include \"AnimationManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../desktop/DesktopTypes.hpp\"\n#include \"../../helpers/AnimatedVariable.hpp\"\n#include \"../../macros.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../desktop/view/LayerSurface.hpp\"\n#include \"../eventLoop/EventLoopManager.hpp\"\n#include \"../../helpers/varlist/VarList.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../event/EventBus.hpp\"\n\n#include <hyprgraphics/color/Color.hpp>\n#include <hyprutils/animation/AnimatedVariable.hpp>\n#include <hyprutils/animation/AnimationManager.hpp>\n\nstatic int wlTick(SP<CEventLoopTimer> self, void* data) {\n    if (g_pAnimationManager)\n        g_pAnimationManager->frameTick();\n\n    return 0;\n}\n\nCHyprAnimationManager::CHyprAnimationManager() {\n    m_animationTimer = makeShared<CEventLoopTimer>(std::chrono::microseconds(500), wlTick, nullptr);\n    if (g_pEventLoopManager) // null in --verify-config mode\n        g_pEventLoopManager->addTimer(m_animationTimer);\n\n    addBezierWithName(\"linear\", Vector2D(0.0, 0.0), Vector2D(1.0, 1.0));\n}\n\ntemplate <Animable VarType>\nstatic void updateVariable(CAnimatedVariable<VarType>& av, const float POINTY, bool warp = false) {\n    if (warp || av.value() == av.goal()) {\n        av.warp(true, false);\n        return;\n    }\n\n    const auto DELTA = av.goal() - av.begun();\n    av.value()       = av.begun() + DELTA * POINTY;\n}\n\nstatic void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float POINTY, bool warp) {\n    if (warp || av.value() == av.goal()) {\n        av.warp(true, false);\n        return;\n    }\n\n    // convert both to OkLab, then lerp that, and convert back.\n    // This is not as fast as just lerping rgb, but it's WAY more precise...\n    // Use the CHyprColor cache for OkLab\n\n    const auto&                L1 = av.begun().asOkLab();\n    const auto&                L2 = av.goal().asOkLab();\n\n    static const auto          lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); };\n\n    const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{\n        .l = lerp(L1.l, L2.l, POINTY),\n        .a = lerp(L1.a, L2.a, POINTY),\n        .b = lerp(L1.b, L2.b, POINTY),\n    };\n\n    av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)};\n}\n\ntemplate <Animable VarType>\nstatic void handleUpdate(CAnimatedVariable<VarType>& av, bool warp) {\n    PHLWINDOW    PWINDOW            = av.m_Context.pWindow.lock();\n    PHLWORKSPACE PWORKSPACE         = av.m_Context.pWorkspace.lock();\n    PHLLS        PLAYER             = av.m_Context.pLayer.lock();\n    PHLMONITOR   PMONITOR           = nullptr;\n    bool         animationsDisabled = warp;\n\n    if (PWINDOW) {\n        if (av.m_Context.eDamagePolicy == AVARDAMAGE_ENTIRE)\n            g_pHyprRenderer->damageWindow(PWINDOW);\n        else if (av.m_Context.eDamagePolicy == AVARDAMAGE_BORDER) {\n            const auto PDECO = PWINDOW->getDecorationByType(DECORATION_BORDER);\n            PDECO->damageEntire();\n        } else if (av.m_Context.eDamagePolicy == AVARDAMAGE_SHADOW) {\n            const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW);\n            PDECO->damageEntire();\n        }\n\n        PMONITOR = PWINDOW->m_monitor.lock();\n        if (!PMONITOR)\n            return;\n\n        animationsDisabled = PWINDOW->m_ruleApplicator->noAnim().valueOr(animationsDisabled);\n    } else if (PWORKSPACE) {\n        PMONITOR = PWORKSPACE->m_monitor.lock();\n        if (!PMONITOR)\n            return;\n\n        // don't damage the whole monitor on workspace change, unless it's a special workspace, because dim/blur etc\n        if (PWORKSPACE->m_isSpecialWorkspace)\n            g_pHyprRenderer->damageMonitor(PMONITOR);\n\n        // TODO: just make this into a damn callback already vax...\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped || w->isHidden() || w->m_workspace != PWORKSPACE)\n                continue;\n\n            if (w->m_isFloating && !w->m_pinned) {\n                // still doing the full damage hack for floating because sometimes when the window\n                // goes through multiple monitors the last rendered frame is missing damage somehow??\n                const CBox windowBoxNoOffset = w->getFullWindowBoundingBox();\n                const CBox monitorBox        = {PMONITOR->m_position, PMONITOR->m_size};\n                if (windowBoxNoOffset.intersection(monitorBox) != windowBoxNoOffset) // on edges between multiple monitors\n                    g_pHyprRenderer->damageWindow(w, true);\n            }\n\n            if (PWORKSPACE->m_isSpecialWorkspace)\n                g_pHyprRenderer->damageWindow(w, true); // hack for special too because it can cross multiple monitors\n        }\n\n        // damage any workspace window that is on any monitor\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!validMapped(w) || w->m_workspace != PWORKSPACE || w->m_pinned)\n                continue;\n\n            g_pHyprRenderer->damageWindow(w);\n        }\n    } else if (PLAYER) {\n        // \"some fucking layers miss 1 pixel???\" -- vaxry\n        CBox expandBox = CBox{PLAYER->m_realPosition->value(), PLAYER->m_realSize->value()};\n        expandBox.expand(5);\n        g_pHyprRenderer->damageBox(expandBox);\n\n        PMONITOR = g_pCompositor->getMonitorFromVector(PLAYER->m_realPosition->goal() + PLAYER->m_realSize->goal() / 2.F);\n        if (!PMONITOR)\n            return;\n        animationsDisabled = animationsDisabled || PLAYER->m_ruleApplicator->noanim().valueOrDefault();\n    }\n\n    const auto SPENT   = av.getPercent();\n    const auto PBEZIER = g_pAnimationManager->getBezier(av.getBezierName());\n    const auto POINTY  = PBEZIER->getYForPoint(SPENT);\n    const bool WARP    = animationsDisabled || SPENT >= 1.f;\n\n    if constexpr (std::same_as<VarType, CHyprColor>)\n        updateColorVariable(av, POINTY, WARP);\n    else\n        updateVariable<VarType>(av, POINTY, WARP);\n\n    av.onUpdate();\n\n    switch (av.m_Context.eDamagePolicy) {\n        case AVARDAMAGE_ENTIRE: {\n            if (PWINDOW) {\n                PWINDOW->updateWindowDecos();\n                g_pHyprRenderer->damageWindow(PWINDOW);\n            } else if (PWORKSPACE) {\n                for (auto const& w : g_pCompositor->m_windows) {\n                    if (!validMapped(w) || w->m_workspace != PWORKSPACE)\n                        continue;\n\n                    w->updateWindowDecos();\n\n                    // damage any workspace window that is on any monitor\n                    if (!w->m_pinned)\n                        g_pHyprRenderer->damageWindow(w);\n                }\n            } else if (PLAYER) {\n                if (PLAYER->m_layer <= 1 && PMONITOR)\n                    PMONITOR->m_blurFBDirty = true;\n\n                // some fucking layers miss 1 pixel???\n                CBox expandBox = CBox{PLAYER->m_realPosition->value(), PLAYER->m_realSize->value()};\n                expandBox.expand(5);\n                g_pHyprRenderer->damageBox(expandBox);\n            }\n            break;\n        }\n        case AVARDAMAGE_BORDER: {\n            RASSERT(PWINDOW, \"Tried to AVARDAMAGE_BORDER a non-window AVAR!\");\n\n            const auto PDECO = PWINDOW->getDecorationByType(DECORATION_BORDER);\n            PDECO->damageEntire();\n\n            break;\n        }\n        case AVARDAMAGE_SHADOW: {\n            RASSERT(PWINDOW, \"Tried to AVARDAMAGE_SHADOW a non-window AVAR!\");\n\n            const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW);\n\n            PDECO->damageEntire();\n\n            break;\n        }\n        default: {\n            break;\n        }\n    }\n\n    // manually schedule a frame\n    if (PMONITOR && !PMONITOR->inFullscreenMode())\n        g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION);\n}\n\nvoid CHyprAnimationManager::tick() {\n    static std::chrono::time_point lastTick = std::chrono::high_resolution_clock::now();\n    m_lastTickTimeMs                        = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - lastTick).count() / 1000.0;\n    lastTick                                = std::chrono::high_resolution_clock::now();\n\n    static auto PANIMENABLED = CConfigValue<Hyprlang::INT>(\"animations:enabled\");\n\n    if (!m_vActiveAnimatedVariables.empty()) {\n        const auto CPY = m_vActiveAnimatedVariables;\n\n        for (const auto& PAV : CPY) {\n            if (!PAV)\n                continue;\n\n            // lock this value while we are doing handleUpdate to avoid a UAF if an update callback destroys it\n            const auto LOCK = PAV.lock();\n\n            // for disabled anims just warp\n            bool warp = !*PANIMENABLED || !PAV->enabled();\n\n            switch (PAV->m_Type) {\n                case AVARTYPE_FLOAT: {\n                    auto pTypedAV = dc<CAnimatedVariable<float>*>(PAV.get());\n                    RASSERT(pTypedAV, \"Failed to upcast animated float\");\n                    handleUpdate(*pTypedAV, warp);\n                } break;\n                case AVARTYPE_VECTOR: {\n                    auto pTypedAV = dc<CAnimatedVariable<Vector2D>*>(PAV.get());\n                    RASSERT(pTypedAV, \"Failed to upcast animated Vector2D\");\n                    handleUpdate(*pTypedAV, warp);\n                } break;\n                case AVARTYPE_COLOR: {\n                    auto pTypedAV = dc<CAnimatedVariable<CHyprColor>*>(PAV.get());\n                    RASSERT(pTypedAV, \"Failed to upcast animated CHyprColor\");\n                    handleUpdate(*pTypedAV, warp);\n                } break;\n                default: UNREACHABLE();\n            }\n        }\n    }\n\n    tickDone();\n}\n\nvoid CHyprAnimationManager::frameTick() {\n    onTicked();\n\n    if (!shouldTickForNext())\n        return;\n\n    if UNLIKELY (!g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState ||\n                 !std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; }))\n        return;\n\n    if (!m_lastTickValid || m_lastTickTimer.getMillis() >= 1.0f) {\n        m_lastTickTimer.reset();\n        m_lastTickValid = true;\n\n        tick();\n        Event::bus()->m_events.tick.emit();\n    }\n\n    if (shouldTickForNext())\n        scheduleTick();\n}\n\nvoid CHyprAnimationManager::scheduleTick() {\n    if (m_tickScheduled)\n        return;\n\n    m_tickScheduled = true;\n\n    if (!m_animationTimer || !g_pEventLoopManager) {\n        m_tickScheduled = false;\n        return;\n    }\n\n    m_animationTimer->updateTimeout(std::chrono::milliseconds(1));\n}\n\nvoid CHyprAnimationManager::onTicked() {\n    m_tickScheduled = false;\n}\n\nvoid CHyprAnimationManager::resetTickState() {\n    m_lastTickValid = false;\n    m_tickScheduled = false;\n}\n\nstd::string CHyprAnimationManager::styleValidInConfigVar(const std::string& config, const std::string& style) {\n    if (config.starts_with(\"window\")) {\n        if (style.starts_with(\"slide\") || style == \"gnome\" || style == \"gnomed\")\n            return \"\";\n        else if (style.starts_with(\"popin\")) {\n            // try parsing\n            float minPerc = 0.f;\n            if (style.find('%') != std::string::npos) {\n                try {\n                    auto percstr = style.substr(style.find_last_of(' '));\n                    minPerc      = std::stoi(percstr.substr(0, percstr.length() - 1));\n                } catch (std::exception& e) { return \"invalid minperc\"; }\n\n                return \"\";\n            }\n\n            minPerc; // fix warning\n\n            return \"\";\n        }\n\n        return \"unknown style\";\n    } else if (config.starts_with(\"workspaces\") || config.starts_with(\"specialWorkspace\")) {\n        if (style == \"slide\" || style == \"slidevert\" || style == \"fade\")\n            return \"\";\n        else if (style.starts_with(\"slide\")) {\n            // try parsing\n            float movePerc = 0.f;\n            if (style.find('%') != std::string::npos) {\n                try {\n                    auto percstr = style.substr(style.find_last_of(' ') + 1);\n                    movePerc     = std::stoi(percstr.substr(0, percstr.length() - 1));\n                } catch (std::exception& e) { return \"invalid movePerc\"; }\n\n                return \"\";\n            }\n\n            movePerc; // fix warning\n\n            return \"\";\n        }\n\n        return \"unknown style\";\n    } else if (config == \"borderangle\") {\n        if (style == \"loop\" || style == \"once\")\n            return \"\";\n        return \"unknown style\";\n    } else if (config.starts_with(\"layers\")) {\n        if (style.empty() || style == \"fade\" || style == \"slide\")\n            return \"\";\n        else if (style.starts_with(\"popin\")) {\n            // try parsing\n            float minPerc = 0.f;\n            if (style.find('%') != std::string::npos) {\n                try {\n                    auto percstr = style.substr(style.find_last_of(' '));\n                    minPerc      = std::stoi(percstr.substr(0, percstr.length() - 1));\n                } catch (std::exception& e) { return \"invalid minperc\"; }\n\n                return \"\";\n            }\n\n            minPerc; // fix warning\n\n            return \"\";\n        }\n        return \"\";\n        return \"unknown style\";\n    } else {\n        return \"animation has no styles\";\n    }\n\n    return \"\";\n}\n"
  },
  {
    "path": "src/managers/animation/AnimationManager.hpp",
    "content": "#pragma once\n\n#include <hyprutils/animation/AnimationManager.hpp>\n#include <hyprutils/animation/AnimatedVariable.hpp>\n\n#include \"../../defines.hpp\"\n#include \"../../helpers/AnimatedVariable.hpp\"\n#include \"../../desktop/DesktopTypes.hpp\"\n#include \"../../helpers/time/Timer.hpp\"\n#include \"../eventLoop/EventLoopTimer.hpp\"\n\nclass CHyprAnimationManager : public Hyprutils::Animation::CAnimationManager {\n  public:\n    CHyprAnimationManager();\n\n    void         tick();\n    void         frameTick();\n    virtual void scheduleTick();\n    virtual void onTicked();\n\n    // Reset tick state after session changes (suspend/wake, lock/unlock)\n    void resetTickState();\n\n    using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig;\n    template <Animable VarType>\n    void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig, eAVarDamagePolicy policy) {\n        constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType<VarType>;\n        pav                                      = makeUnique<CAnimatedVariable<VarType>>();\n\n        pav->create2(EAVTYPE, sc<Hyprutils::Animation::CAnimationManager*>(this), pav, v);\n        pav->setConfig(pConfig);\n        pav->m_Context.eDamagePolicy = policy;\n    }\n\n    template <Animable VarType>\n    void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig, PHLWINDOW pWindow, eAVarDamagePolicy policy) {\n        createAnimation(v, pav, pConfig, policy);\n        pav->m_Context.pWindow = pWindow;\n    }\n    template <Animable VarType>\n    void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig, PHLWORKSPACE pWorkspace, eAVarDamagePolicy policy) {\n        createAnimation(v, pav, pConfig, policy);\n        pav->m_Context.pWorkspace = pWorkspace;\n    }\n    template <Animable VarType>\n    void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig, PHLLS pLayer, eAVarDamagePolicy policy) {\n        createAnimation(v, pav, pConfig, policy);\n        pav->m_Context.pLayer = pLayer;\n    }\n\n    std::string         styleValidInConfigVar(const std::string&, const std::string&);\n\n    SP<CEventLoopTimer> m_animationTimer;\n\n    float               m_lastTickTimeMs;\n\n  private:\n    bool   m_tickScheduled = false;\n    bool   m_lastTickValid = false;\n    CTimer m_lastTickTimer;\n};\n\ninline UP<CHyprAnimationManager> g_pAnimationManager;\n"
  },
  {
    "path": "src/managers/animation/DesktopAnimationManager.cpp",
    "content": "#include \"DesktopAnimationManager.hpp\"\n\n#include <algorithm>\n#include <optional>\n\n#include \"../../desktop/view/LayerSurface.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../desktop/view/Group.hpp\"\n#include \"../../desktop/Workspace.hpp\"\n\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"desktop/DesktopTypes.hpp\"\n#include \"wlr-layer-shell-unstable-v1.hpp\"\n\nvoid CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType type, bool force) {\n    const bool CLOSE = type == ANIMATION_TYPE_OUT;\n\n    if (CLOSE)\n        *pWindow->m_alpha = 0.F;\n    else {\n        pWindow->m_alpha->setValueAndWarp(0.F);\n        *pWindow->m_alpha = 1.F;\n    }\n\n    if (!CLOSE) {\n        pWindow->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"windowsIn\"));\n        pWindow->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"windowsIn\"));\n        pWindow->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadeIn\"));\n    } else {\n        pWindow->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"windowsOut\"));\n        pWindow->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"windowsOut\"));\n        pWindow->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadeOut\"));\n    }\n\n    std::string ANIMSTYLE = pWindow->m_realPosition->getStyle();\n    std::ranges::transform(ANIMSTYLE, ANIMSTYLE.begin(), ::tolower);\n\n    CVarList animList(ANIMSTYLE, 0, 's');\n\n    // if the window is not being animated, that means the layout set a fixed size for it, don't animate.\n    if (!pWindow->m_realPosition->isBeingAnimated() && !pWindow->m_realSize->isBeingAnimated() && !force)\n        return;\n\n    // if the animation is disabled and we are leaving, ignore the anim to prevent the snapshot being fucked\n    if (!pWindow->m_realPosition->enabled() && !force)\n        return;\n\n    if (pWindow->m_ruleApplicator->animationStyle().hasValue()) {\n        const auto STYLE = pWindow->m_ruleApplicator->animationStyle().value();\n        // the window has config'd special anim\n        if (STYLE.starts_with(\"slide\")) {\n            CVarList animList2(STYLE, 0, 's');\n            animationSlide(pWindow, animList2[1], CLOSE);\n        } else if (STYLE == \"gnomed\" || STYLE == \"gnome\")\n            animationGnomed(pWindow, CLOSE);\n        else {\n            // anim popin, fallback\n\n            float minPerc = 0.f;\n            if (STYLE.find(\"%\") != std::string::npos) {\n                try {\n                    auto percstr = STYLE.substr(STYLE.find_last_of(' '));\n                    minPerc      = std::stoi(percstr.substr(0, percstr.length() - 1));\n                } catch (std::exception& e) {\n                    ; // oops\n                }\n            }\n\n            animationPopin(pWindow, CLOSE, minPerc / 100.f);\n        }\n    } else {\n        if (animList[0] == \"slide\")\n            animationSlide(pWindow, animList[1], CLOSE);\n        else if (animList[0] == \"gnomed\" || animList[0] == \"gnome\")\n            animationGnomed(pWindow, CLOSE);\n        else {\n            // anim popin, fallback\n\n            float minPerc = 0.f;\n            if (!ANIMSTYLE.starts_with(\"%\")) {\n                try {\n                    auto percstr = ANIMSTYLE.substr(ANIMSTYLE.find_last_of(' '));\n                    minPerc      = std::stoi(percstr.substr(0, percstr.length() - 1));\n                } catch (std::exception& e) {\n                    ; // oops\n                }\n            }\n\n            animationPopin(pWindow, CLOSE, minPerc / 100.f);\n        }\n    }\n}\n\nvoid CDesktopAnimationManager::startAnimation(PHLLS ls, eAnimationType type, bool instant) {\n    const bool IN = type == ANIMATION_TYPE_IN;\n\n    if (IN) {\n        ls->m_alpha->setValueAndWarp(0.F);\n        *ls->m_alpha = 1.F;\n    } else\n        *ls->m_alpha = 0.F;\n\n    if (IN) {\n        ls->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"layersIn\"));\n        ls->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"layersIn\"));\n        ls->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadeLayersIn\"));\n    } else {\n        ls->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"layersOut\"));\n        ls->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"layersOut\"));\n        ls->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(\"fadeLayersOut\"));\n    }\n\n    const auto ANIMSTYLE = ls->m_ruleApplicator->animationStyle().valueOr(ls->m_realPosition->getStyle());\n    if (ANIMSTYLE.starts_with(\"slide\")) {\n        // get closest edge\n        const auto MIDDLE = ls->m_geometry.middle();\n\n        const auto PMONITOR = g_pCompositor->getMonitorFromVector(MIDDLE);\n\n        if (!PMONITOR) { // can rarely happen on exit\n            ls->m_alpha->setValueAndWarp(IN ? 1.F : 0.F);\n            return;\n        }\n\n        int      force = -1;\n\n        CVarList args(ANIMSTYLE, 0, 's');\n        if (args.size() > 1) {\n            const auto ARG2 = args[1];\n            if (ARG2 == \"top\")\n                force = 0;\n            else if (ARG2 == \"bottom\")\n                force = 1;\n            else if (ARG2 == \"left\")\n                force = 2;\n            else if (ARG2 == \"right\")\n                force = 3;\n        }\n\n        const std::array<Vector2D, 4> edgePoints = {\n            PMONITOR->m_position + Vector2D{PMONITOR->m_size.x / 2, 0.0},\n            PMONITOR->m_position + Vector2D{PMONITOR->m_size.x / 2, PMONITOR->m_size.y},\n            PMONITOR->m_position + Vector2D{0.0, PMONITOR->m_size.y},\n            PMONITOR->m_position + Vector2D{PMONITOR->m_size.x, PMONITOR->m_size.y / 2},\n        };\n\n        float closest = std::numeric_limits<float>::max();\n        int   leader  = force;\n        if (leader == -1) {\n            for (size_t i = 0; i < 4; ++i) {\n                float dist = MIDDLE.distance(edgePoints[i]);\n                if (dist < closest) {\n                    leader  = i;\n                    closest = dist;\n                }\n            }\n        }\n\n        ls->m_realSize->setValueAndWarp(ls->m_geometry.size());\n\n        Vector2D prePos;\n\n        switch (leader) {\n            case 0:\n                // TOP\n                prePos = {ls->m_geometry.x, PMONITOR->m_position.y - ls->m_geometry.h};\n                break;\n            case 1:\n                // BOTTOM\n                prePos = {ls->m_geometry.x, PMONITOR->m_position.y + PMONITOR->m_size.y};\n                break;\n            case 2:\n                // LEFT\n                prePos = {PMONITOR->m_position.x - ls->m_geometry.w, ls->m_geometry.y};\n                break;\n            case 3:\n                // RIGHT\n                prePos = {PMONITOR->m_position.x + PMONITOR->m_size.x, ls->m_geometry.y};\n                break;\n            default: UNREACHABLE();\n        }\n\n        if (IN) {\n            ls->m_realPosition->setValueAndWarp(prePos);\n            *ls->m_realPosition = ls->m_geometry.pos();\n        } else {\n            ls->m_realPosition->setValueAndWarp(ls->m_geometry.pos());\n            *ls->m_realPosition = prePos;\n        }\n\n    } else if (ANIMSTYLE.starts_with(\"popin\")) {\n        float minPerc = 0.f;\n        if (ANIMSTYLE.find(\"%\") != std::string::npos) {\n            try {\n                auto percstr = ANIMSTYLE.substr(ANIMSTYLE.find_last_of(' '));\n                minPerc      = std::stoi(percstr.substr(0, percstr.length() - 1));\n            } catch (std::exception& e) {\n                ; // oops\n            }\n        }\n\n        minPerc *= 0.01;\n\n        const auto GOALSIZE = (ls->m_geometry.size() * minPerc).clamp({5, 5});\n        const auto GOALPOS  = ls->m_geometry.pos() + (ls->m_geometry.size() - GOALSIZE) / 2.f;\n\n        ls->m_alpha->setValueAndWarp(IN ? 0.f : 1.f);\n        *ls->m_alpha = IN ? 1.f : 0.f;\n\n        if (IN) {\n            ls->m_realSize->setValueAndWarp(GOALSIZE);\n            ls->m_realPosition->setValueAndWarp(GOALPOS);\n            *ls->m_realSize     = ls->m_geometry.size();\n            *ls->m_realPosition = ls->m_geometry.pos();\n        } else {\n            ls->m_realSize->setValueAndWarp(ls->m_geometry.size());\n            ls->m_realPosition->setValueAndWarp(ls->m_geometry.pos());\n            *ls->m_realSize     = GOALSIZE;\n            *ls->m_realPosition = GOALPOS;\n        }\n    } else {\n        // fade\n        ls->m_realPosition->setValueAndWarp(ls->m_geometry.pos());\n        ls->m_realSize->setValueAndWarp(ls->m_geometry.size());\n        *ls->m_alpha = IN ? 1.f : 0.f;\n    }\n\n    if (instant) {\n        ls->m_realPosition->warp();\n        ls->m_realSize->warp();\n        ls->m_alpha->warp();\n    }\n}\n\nvoid CDesktopAnimationManager::startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left, bool instant, std::optional<std::string> style) {\n    const bool IN = type == ANIMATION_TYPE_IN;\n\n    if (!instant) {\n        const std::string ANIMNAME = std::format(\"{}{}\", ws->m_isSpecialWorkspace ? \"specialWorkspace\" : \"workspaces\", IN ? \"In\" : \"Out\");\n\n        ws->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(ANIMNAME));\n        ws->m_renderOffset->setConfig(g_pConfigManager->getAnimationPropertyConfig(ANIMNAME));\n    }\n    static auto PWORKSPACEGAP = CConfigValue<Hyprlang::INT>(\"general:gaps_workspaces\");\n    const auto  PMONITOR      = ws->m_monitor.lock();\n    const auto  ANIMSTYLE     = style.value_or(ws->m_alpha->getStyle());\n\n    float       movePerc = 100.f;\n    // inverted for some reason. TODO: fix the cause\n    bool vert = ANIMSTYLE.starts_with(\"slidevert\") || ANIMSTYLE.starts_with(\"slidefadevert\");\n\n    // set floating windows offset callbacks\n    ws->m_renderOffset->setUpdateCallback([weak = PHLWORKSPACEREF{ws}](auto) {\n        if (!weak)\n            return;\n\n        for (auto const& w : g_pCompositor->m_windows) {\n            if (!validMapped(w) || w->workspaceID() != weak->m_id)\n                continue;\n\n            w->onWorkspaceAnimUpdate();\n        };\n    });\n\n    CVarList args(ANIMSTYLE, 0, 's');\n    if (args.size() > 1) {\n        const auto ARG2 = args[1];\n        if (ARG2 == \"top\") {\n            left = false;\n            vert = true;\n        } else if (ARG2 == \"bottom\") {\n            left = true;\n            vert = true;\n        } else if (ARG2 == \"left\") {\n            left = false;\n            vert = false;\n        } else if (ARG2 == \"right\") {\n            left = true;\n            vert = false;\n        }\n    }\n\n    const auto percstr = args[args.size() - 1];\n    if (percstr.ends_with('%')) {\n        try {\n            movePerc = std::stoi(percstr.substr(0, percstr.length() - 1));\n        } catch (std::exception& e) { Log::logger->log(Log::ERR, \"Error in startAnim: invalid percentage\"); }\n    }\n\n    if (ANIMSTYLE.starts_with(\"slidefade\")) {\n\n        ws->m_alpha->setValueAndWarp(1.f);\n        ws->m_renderOffset->setValueAndWarp(Vector2D(0, 0));\n\n        if (vert) {\n            if (IN) {\n                ws->m_alpha->setValueAndWarp(0.f);\n                ws->m_renderOffset->setValueAndWarp(Vector2D(0.0, (left ? PMONITOR->m_size.y : -PMONITOR->m_size.y) * (movePerc / 100.f)));\n                *ws->m_alpha        = 1.f;\n                *ws->m_renderOffset = Vector2D(0, 0);\n            } else {\n                ws->m_alpha->setValueAndWarp(1.f);\n                *ws->m_alpha        = 0.f;\n                *ws->m_renderOffset = Vector2D(0.0, (left ? -PMONITOR->m_size.y : PMONITOR->m_size.y) * (movePerc / 100.f));\n            }\n        } else {\n            if (IN) {\n                ws->m_alpha->setValueAndWarp(0.f);\n                ws->m_renderOffset->setValueAndWarp(Vector2D((left ? PMONITOR->m_size.x : -PMONITOR->m_size.x) * (movePerc / 100.f), 0.0));\n                *ws->m_alpha        = 1.f;\n                *ws->m_renderOffset = Vector2D(0, 0);\n            } else {\n                ws->m_alpha->setValueAndWarp(1.f);\n                *ws->m_alpha        = 0.f;\n                *ws->m_renderOffset = Vector2D((left ? -PMONITOR->m_size.x : PMONITOR->m_size.x) * (movePerc / 100.f), 0.0);\n            }\n        }\n    } else if (ANIMSTYLE == \"fade\") {\n        ws->m_renderOffset->setValueAndWarp(Vector2D(0, 0)); // fix a bug, if switching from slide -> fade.\n\n        if (IN) {\n            ws->m_alpha->setValueAndWarp(0.f);\n            *ws->m_alpha = 1.f;\n        } else {\n            ws->m_alpha->setValueAndWarp(1.f);\n            *ws->m_alpha = 0.f;\n        }\n    } else if (vert) {\n        const auto YDISTANCE = (PMONITOR->m_size.y + *PWORKSPACEGAP) * (movePerc / 100.f);\n        ws->m_alpha->setValueAndWarp(1.f); // fix a bug, if switching from fade -> slide.\n\n        if (IN) {\n            ws->m_renderOffset->setValueAndWarp(Vector2D(0.0, left ? YDISTANCE : -YDISTANCE));\n            *ws->m_renderOffset = Vector2D(0, 0);\n        } else {\n            *ws->m_renderOffset = Vector2D(0.0, left ? -YDISTANCE : YDISTANCE);\n        }\n\n    } else {\n        // fallback is slide\n        const auto XDISTANCE = (PMONITOR->m_size.x + *PWORKSPACEGAP) * (movePerc / 100.f);\n        ws->m_alpha->setValueAndWarp(1.f); // fix a bug, if switching from fade -> slide.\n\n        if (IN) {\n            ws->m_renderOffset->setValueAndWarp(Vector2D(left ? XDISTANCE : -XDISTANCE, 0.0));\n            *ws->m_renderOffset = Vector2D(0, 0);\n        } else {\n            *ws->m_renderOffset = Vector2D(left ? -XDISTANCE : XDISTANCE, 0.0);\n        }\n    }\n\n    if (ws->m_isSpecialWorkspace) {\n        // required for open/close animations\n        if (IN) {\n            ws->m_alpha->setValueAndWarp(0.f);\n            *ws->m_alpha = 1.f;\n        } else {\n            ws->m_alpha->setValueAndWarp(1.f);\n            *ws->m_alpha = 0.f;\n        }\n    }\n\n    if (instant) {\n        ws->m_renderOffset->warp();\n        ws->m_alpha->warp();\n    }\n}\n\nvoid CDesktopAnimationManager::animationPopin(PHLWINDOW pWindow, bool close, float minPerc) {\n    const auto GOALPOS  = pWindow->m_realPosition->goal();\n    const auto GOALSIZE = pWindow->m_realSize->goal();\n\n    if (!close) {\n        pWindow->m_realSize->setValue((GOALSIZE * minPerc).clamp({5, 5}, {GOALSIZE.x, GOALSIZE.y}));\n        pWindow->m_realPosition->setValue(GOALPOS + GOALSIZE / 2.f - pWindow->m_realSize->value() / 2.f);\n    } else {\n        *pWindow->m_realSize     = (GOALSIZE * minPerc).clamp({5, 5}, {GOALSIZE.x, GOALSIZE.y});\n        *pWindow->m_realPosition = GOALPOS + GOALSIZE / 2.f - pWindow->m_realSize->goal() / 2.f;\n    }\n}\n\nvoid CDesktopAnimationManager::animationSlide(PHLWINDOW pWindow, std::string force, bool close) {\n    pWindow->m_realSize->warp(false); // size we preserve in slide\n\n    const auto GOALPOS  = pWindow->m_realPosition->goal();\n    const auto GOALSIZE = pWindow->m_realSize->goal();\n\n    const auto PMONITOR = pWindow->m_monitor.lock();\n\n    if (!PMONITOR)\n        return; // unsafe state most likely\n\n    Vector2D posOffset;\n\n    if (!force.empty()) {\n        if (force == \"bottom\")\n            posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y + PMONITOR->m_size.y);\n        else if (force == \"left\")\n            posOffset = GOALPOS - Vector2D(GOALSIZE.x, 0.0);\n        else if (force == \"right\")\n            posOffset = GOALPOS + Vector2D(GOALSIZE.x, 0.0);\n        else\n            posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y - GOALSIZE.y);\n\n        if (!close)\n            pWindow->m_realPosition->setValue(posOffset);\n        else\n            *pWindow->m_realPosition = posOffset;\n\n        return;\n    }\n\n    const auto MIDPOINT = GOALPOS + GOALSIZE / 2.f;\n    const auto MONBOX   = PMONITOR->logicalBox();\n\n    // find the closest edge to midpoint\n    // CSS style, top right bottom left\n    std::array<float, 4> distances = {\n        MIDPOINT.y - MONBOX.y,            //\n        MONBOX.x + MONBOX.w - MIDPOINT.x, //\n        MONBOX.y + MONBOX.h - MIDPOINT.y, //\n        MIDPOINT.x - MONBOX.x,            //\n    };\n\n    const auto MIN_DIST = std::min({distances[0], distances[1], distances[2], distances[3]});\n    if (MIN_DIST == distances[2])\n        posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y + PMONITOR->m_size.y);\n    else if (MIN_DIST == distances[3])\n        posOffset = GOALPOS - Vector2D(GOALSIZE.x, 0.0);\n    else if (MIN_DIST == distances[1])\n        posOffset = GOALPOS + Vector2D(GOALSIZE.x, 0.0);\n    else\n        posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y - GOALSIZE.y);\n\n    if (!close)\n        pWindow->m_realPosition->setValue(posOffset);\n    else\n        *pWindow->m_realPosition = posOffset;\n}\n\nvoid CDesktopAnimationManager::animationGnomed(PHLWINDOW pWindow, bool close) {\n    const auto GOALPOS  = pWindow->m_realPosition->goal();\n    const auto GOALSIZE = pWindow->m_realSize->goal();\n\n    if (close) {\n        *pWindow->m_realPosition = GOALPOS + Vector2D{0.F, GOALSIZE.y / 2.F};\n        *pWindow->m_realSize     = Vector2D{GOALSIZE.x, 0.F};\n    } else {\n        pWindow->m_realPosition->setValueAndWarp(GOALPOS + Vector2D{0.F, GOALSIZE.y / 2.F});\n        pWindow->m_realSize->setValueAndWarp(Vector2D{GOALSIZE.x, 0.F});\n        *pWindow->m_realPosition = GOALPOS;\n        *pWindow->m_realSize     = GOALSIZE;\n    }\n}\n\nvoid CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnimationType type) {\n    if (!ws)\n        return;\n\n    const auto FULLSCREEN = type == ANIMATION_TYPE_IN;\n\n    const auto FSWINDOW = ws->getFullscreenWindow();\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace == ws) {\n\n            if (w->m_fadingOut || w->m_pinned || w->isFullscreen())\n                continue;\n\n            if (!FULLSCREEN)\n                *w->m_alpha = 1.F;\n            else if (!w->isFullscreen()) {\n                const bool CREATED_OVER_FS   = w->m_createdOverFullscreen;\n                const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(w);\n                *w->m_alpha                  = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f;\n            }\n        }\n    }\n\n    const auto PMONITOR = ws->m_monitor.lock();\n\n    if (ws->m_id == PMONITOR->activeWorkspaceID() || ws->m_id == PMONITOR->activeSpecialWorkspaceID()) {\n        for (auto const& ls : PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {\n            if (!ls->m_fadingOut && !ls->m_aboveFullscreen)\n                *ls->m_alpha = FULLSCREEN && ws->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f;\n        }\n    }\n}\n\nvoid CDesktopAnimationManager::setFullscreenFloatingFade(PHLWINDOW pWindow, float fade) {\n    if (pWindow->m_fadingOut || !pWindow->m_isFloating)\n        return;\n\n    *pWindow->m_alpha = fade;\n}\n\nvoid CDesktopAnimationManager::overrideFullscreenFadeAmount(PHLWORKSPACE ws, float fade, PHLWINDOW exclude) {\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w == exclude)\n            continue;\n\n        if (w->m_workspace == ws) {\n            if (w->m_fadingOut || w->m_pinned || w->isFullscreen())\n                continue;\n\n            *w->m_alpha = fade;\n        }\n    }\n\n    const auto PMONITOR = ws->m_monitor.lock();\n\n    if (ws->m_id == PMONITOR->activeWorkspaceID() || ws->m_id == PMONITOR->activeSpecialWorkspaceID()) {\n        for (auto const& ls : PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {\n            if (!ls->m_fadingOut)\n                *ls->m_alpha = fade;\n        }\n    }\n}\n"
  },
  {
    "path": "src/managers/animation/DesktopAnimationManager.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../desktop/DesktopTypes.hpp\"\n#include <string>\n#include <optional>\n\nclass CDesktopAnimationManager {\n  public:\n    enum eAnimationType : uint8_t {\n        ANIMATION_TYPE_IN = 0,\n        ANIMATION_TYPE_OUT,\n    };\n\n    void startAnimation(PHLWINDOW w, eAnimationType type, bool force = false);\n    void startAnimation(PHLLS ls, eAnimationType type, bool instant = false);\n    void startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left = true, bool instant = false, std::optional<std::string> style = std::nullopt);\n\n    void setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnimationType type);\n    void setFullscreenFloatingFade(PHLWINDOW pWindow, float fade);\n    void overrideFullscreenFadeAmount(PHLWORKSPACE ws, float fade, PHLWINDOW exclude = nullptr);\n\n  private:\n    void animationPopin(PHLWINDOW w, bool close = false, float minPerc = 0.f);\n    void animationSlide(PHLWINDOW w, std::string force = \"\", bool close = false);\n    void animationGnomed(PHLWINDOW w, bool close = false);\n};\n\ninline UP<CDesktopAnimationManager> g_pDesktopAnimationManager = makeUnique<CDesktopAnimationManager>();\n"
  },
  {
    "path": "src/managers/cursor/CursorShapeOverrideController.cpp",
    "content": "#include \"CursorShapeOverrideController.hpp\"\n\n#include <ranges>\n\nusing namespace Cursor;\n\nvoid CShapeOverrideController::setOverride(const std::string& name, eCursorShapeOverrideGroup group) {\n    if (m_overrides[group] == name)\n        return;\n\n    m_overrides[group] = name;\n\n    recheckOverridesResendIfChanged();\n}\n\nvoid CShapeOverrideController::unsetOverride(eCursorShapeOverrideGroup group) {\n    if (m_overrides[group].empty())\n        return;\n\n    m_overrides[group] = \"\";\n\n    recheckOverridesResendIfChanged();\n}\n\nvoid CShapeOverrideController::recheckOverridesResendIfChanged() {\n    for (const auto& s : m_overrides | std::views::reverse) {\n        if (s.empty())\n            continue;\n\n        if (s == m_overrideShape)\n            return;\n\n        m_overrideShape = s;\n        m_events.overrideChanged.emit(s);\n        return;\n    }\n\n    if (m_overrideShape.empty())\n        return;\n\n    m_overrideShape = \"\";\n    m_events.overrideChanged.emit(\"\");\n}\n"
  },
  {
    "path": "src/managers/cursor/CursorShapeOverrideController.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n\n#include <array>\n#include <string>\n\nnamespace Cursor {\n    enum eCursorShapeOverrideGroup : uint8_t {\n        // unknown group - lowest priority\n        CURSOR_OVERRIDE_UNKNOWN = 0,\n        // window edges for resizing from edge\n        CURSOR_OVERRIDE_WINDOW_EDGE,\n        // Drag and drop\n        CURSOR_OVERRIDE_DND,\n        // special action: Interactive::CDrag, kill, etc.\n        CURSOR_OVERRIDE_SPECIAL_ACTION,\n\n        //\n        CURSOR_OVERRIDE_END,\n    };\n\n    class CShapeOverrideController {\n      public:\n        CShapeOverrideController()  = default;\n        ~CShapeOverrideController() = default;\n\n        CShapeOverrideController(const CShapeOverrideController&) = delete;\n        CShapeOverrideController(CShapeOverrideController&)       = delete;\n        CShapeOverrideController(CShapeOverrideController&&)      = delete;\n\n        void setOverride(const std::string& name, eCursorShapeOverrideGroup group);\n        void unsetOverride(eCursorShapeOverrideGroup group);\n\n        struct {\n            // if string is empty, override was cleared\n            CSignalT<const std::string&> overrideChanged;\n        } m_events;\n\n      private:\n        void                                         recheckOverridesResendIfChanged();\n\n        std::array<std::string, CURSOR_OVERRIDE_END> m_overrides;\n        std::string                                  m_overrideShape;\n    };\n\n    inline UP<CShapeOverrideController> overrideController = makeUnique<CShapeOverrideController>();\n};"
  },
  {
    "path": "src/managers/eventLoop/EventLoopManager.cpp",
    "content": "#include \"EventLoopManager.hpp\"\n#include \"../../debug/log/Logger.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigWatcher.hpp\"\n\n#include <algorithm>\n#include <limits>\n#include <ranges>\n\n#include <sys/timerfd.h>\n#include <ctime>\n\n#include <aquamarine/backend/Backend.hpp>\nusing namespace Hyprutils::OS;\n\n#define TIMESPEC_NSEC_PER_SEC 1000000000L\n\nCEventLoopManager::CEventLoopManager(wl_display* display, wl_event_loop* wlEventLoop) {\n    m_timers.timerfd  = CFileDescriptor{timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC)};\n    m_wayland.loop    = wlEventLoop;\n    m_wayland.display = display;\n}\n\nCEventLoopManager::~CEventLoopManager() {\n    for (auto const& [_, eventSourceData] : m_aqEventSources) {\n        wl_event_source_remove(eventSourceData.eventSource);\n    }\n\n    m_readableWaiters.clear();\n\n    if (m_wayland.eventSource)\n        wl_event_source_remove(m_wayland.eventSource);\n    if (m_idle.eventSource)\n        wl_event_source_remove(m_idle.eventSource);\n    if (m_configWatcherInotifySource)\n        wl_event_source_remove(m_configWatcherInotifySource);\n}\n\nstatic int timerWrite(int fd, uint32_t mask, void* data) {\n    if (!CFileDescriptor::isReadable(fd))\n        Log::logger->log(Log::ERR, \"timerWrite: triggered a non readable event on fd : {}\", fd);\n    else {\n        uint64_t expirations;\n        read(fd, &expirations, sizeof(expirations));\n    }\n\n    g_pEventLoopManager->onTimerFire();\n    return 0;\n}\n\nstatic int aquamarineFDWrite(int fd, uint32_t mask, void* data) {\n    auto POLLFD = sc<Aquamarine::SPollFD*>(data);\n    POLLFD->onSignal();\n    return 0;\n}\n\nstatic int configWatcherWrite(int fd, uint32_t mask, void* data) {\n    g_pConfigWatcher->onInotifyEvent();\n    return 0;\n}\n\nstatic int handleWaiterFD(int fd, uint32_t mask, void* data) {\n    auto waiter = sc<CEventLoopManager::SReadableWaiter*>(data);\n\n    if (!waiter) {\n        Log::logger->log(Log::ERR, \"handleWaiterFD: failed casting waiter\");\n        return 0;\n    }\n\n    if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {\n        Log::logger->log(Log::ERR, \"handleWaiterFD: readable waiter error\");\n        g_pEventLoopManager->onFdReadableFail(waiter);\n        return 0;\n    }\n\n    if (mask & WL_EVENT_READABLE)\n        g_pEventLoopManager->onFdReadable(waiter);\n\n    return 0;\n}\n\nvoid CEventLoopManager::onFdReadable(SReadableWaiter* waiter) {\n    auto it = std::ranges::find_if(m_readableWaiters, [waiter](const UP<SReadableWaiter>& w) { return waiter == w.get() && w->fd == waiter->fd && w->source == waiter->source; });\n\n    // ???\n    if (it == m_readableWaiters.end())\n        return;\n\n    if (waiter->source) { // remove even_source if fn() somehow causes a reentry\n        wl_event_source_remove(waiter->source);\n        waiter->source = nullptr;\n    }\n\n    UP<SReadableWaiter> taken = std::move(*it);\n    m_readableWaiters.erase(it);\n\n    if (taken->fn)\n        taken->fn();\n}\n\nvoid CEventLoopManager::onFdReadableFail(SReadableWaiter* waiter) {\n    auto it = std::ranges::find_if(m_readableWaiters, [waiter](const UP<SReadableWaiter>& w) { return waiter == w.get() && w->fd == waiter->fd && w->source == waiter->source; });\n\n    // ???\n    if (it == m_readableWaiters.end())\n        return;\n\n    m_readableWaiters.erase(it);\n}\n\nvoid CEventLoopManager::enterLoop() {\n    m_wayland.eventSource = wl_event_loop_add_fd(m_wayland.loop, m_timers.timerfd.get(), WL_EVENT_READABLE, timerWrite, nullptr);\n\n    if (const auto& FD = g_pConfigWatcher->getInotifyFD(); FD.isValid())\n        m_configWatcherInotifySource = wl_event_loop_add_fd(m_wayland.loop, FD.get(), WL_EVENT_READABLE, configWatcherWrite, nullptr);\n\n    syncPollFDs();\n    m_listeners.pollFDsChanged = g_pCompositor->m_aqBackend->events.pollFDsChanged.listen([this] { syncPollFDs(); });\n\n    // if we have a session, dispatch it to get the pending input devices\n    if (g_pCompositor->m_aqBackend->hasSession())\n        g_pCompositor->m_aqBackend->session->dispatchPendingEventsAsync();\n\n    wl_display_run(m_wayland.display);\n\n    Log::logger->log(Log::DEBUG, \"Kicked off the event loop! :(\");\n}\n\nvoid CEventLoopManager::onTimerFire() {\n    const auto CPY = m_timers.timers;\n    for (auto const& t : CPY) {\n        if (t.strongRef() > 2 /* if it's 2, it was lost. Don't call it. */ && t->passed() && !t->cancelled())\n            t->call(t);\n    }\n\n    scheduleRecalc();\n}\n\nvoid CEventLoopManager::addTimer(SP<CEventLoopTimer> timer) {\n    if (std::ranges::contains(m_timers.timers, timer))\n        return;\n    m_timers.timers.emplace_back(timer);\n    scheduleRecalc();\n}\n\nvoid CEventLoopManager::removeTimer(SP<CEventLoopTimer> timer) {\n    if (!std::ranges::contains(m_timers.timers, timer))\n        return;\n    std::erase_if(m_timers.timers, [timer](const auto& t) { return timer == t; });\n    scheduleRecalc();\n}\n\nstatic void timespecAddNs(timespec* pTimespec, int64_t delta) {\n    auto delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC;\n    auto delta_s_high = delta / TIMESPEC_NSEC_PER_SEC;\n\n    pTimespec->tv_sec += delta_s_high;\n\n    pTimespec->tv_nsec += delta_ns_low;\n    if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) {\n        pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC;\n        ++pTimespec->tv_sec;\n    }\n}\n\nvoid CEventLoopManager::scheduleRecalc() {\n    // do not do it instantly, do it later. Avoid recursive access to the timer\n    // vector, it could be catastrophic if we modify it while iterating\n\n    if (m_timers.recalcScheduled)\n        return;\n\n    m_timers.recalcScheduled = true;\n\n    doLater([this] { nudgeTimers(); });\n}\n\nvoid CEventLoopManager::nudgeTimers() {\n    m_timers.recalcScheduled = false;\n\n    // remove timers that have gone missing\n    std::erase_if(m_timers.timers, [](const auto& t) { return t.strongRef() <= 1; });\n\n    long nextTimerUs = 10L * 1000 * 1000; // 10s\n\n    for (auto const& t : m_timers.timers) {\n        if (auto const& µs = t->leftUs(); µs < nextTimerUs)\n            nextTimerUs = µs;\n    }\n\n    nextTimerUs = std::clamp(nextTimerUs + 1, 1L, std::numeric_limits<long>::max());\n\n    timespec now;\n    clock_gettime(CLOCK_MONOTONIC, &now);\n    timespecAddNs(&now, nextTimerUs * 1000L);\n\n    itimerspec ts = {.it_value = now};\n\n    timerfd_settime(m_timers.timerfd.get(), TFD_TIMER_ABSTIME, &ts, nullptr);\n}\n\nvoid CEventLoopManager::doLater(const std::function<void()>& fn) {\n    m_idle.fns.emplace_back(fn);\n\n    if (m_idle.eventSource)\n        return;\n\n    m_idle.eventSource = wl_event_loop_add_idle(\n        m_wayland.loop,\n        [](void* data) {\n            auto IDLE = sc<CEventLoopManager::SIdleData*>(data);\n            auto fns  = std::move(IDLE->fns);\n            IDLE->fns.clear();\n            IDLE->eventSource = nullptr;\n            for (auto& f : fns) {\n                if (f)\n                    f();\n            }\n        },\n        &m_idle);\n}\n\nvoid CEventLoopManager::doOnReadable(CFileDescriptor fd, std::function<void()>&& fn) {\n    if (!fd.isValid() || fd.isReadable()) {\n        fn();\n        return;\n    }\n\n    auto& waiter   = m_readableWaiters.emplace_back(makeUnique<SReadableWaiter>(nullptr, std::move(fd), std::move(fn)));\n    waiter->source = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, waiter->fd.get(), WL_EVENT_READABLE, ::handleWaiterFD, waiter.get());\n}\n\nvoid CEventLoopManager::syncPollFDs() {\n    auto aqPollFDs = g_pCompositor->m_aqBackend->getPollFDs();\n\n    std::erase_if(m_aqEventSources, [&](const auto& item) {\n        auto const& [fd, eventSourceData] = item;\n\n        // If no pollFD has the same fd, remove this event source\n        const bool shouldRemove = std::ranges::none_of(aqPollFDs, [&](const auto& pollFD) { return pollFD->fd == fd; });\n\n        if (shouldRemove)\n            wl_event_source_remove(eventSourceData.eventSource);\n\n        return shouldRemove;\n    });\n\n    for (auto& fd : aqPollFDs | std::views::filter([&](SP<Aquamarine::SPollFD> fd) { return !m_aqEventSources.contains(fd->fd); })) {\n        auto eventSource         = wl_event_loop_add_fd(m_wayland.loop, fd->fd, WL_EVENT_READABLE, aquamarineFDWrite, fd.get());\n        m_aqEventSources[fd->fd] = {.pollFD = fd, .eventSource = eventSource};\n    }\n}\n"
  },
  {
    "path": "src/managers/eventLoop/EventLoopManager.hpp",
    "content": "#pragma once\n\n#include <condition_variable>\n#include <map>\n#include <mutex>\n#include <thread>\n#include <wayland-server.h>\n#include \"../../helpers/signal/Signal.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\n#include \"EventLoopTimer.hpp\"\n\nnamespace Aquamarine {\n    struct SPollFD;\n};\n\nclass CEventLoopManager {\n  public:\n    CEventLoopManager(wl_display* display, wl_event_loop* wlEventLoop);\n    ~CEventLoopManager();\n\n    void enterLoop();\n\n    // Note: will remove the timer if the ptr is lost.\n    void addTimer(SP<CEventLoopTimer> timer);\n    void removeTimer(SP<CEventLoopTimer> timer);\n\n    void onTimerFire();\n\n    // schedules a recalc of the timers\n    void scheduleRecalc();\n\n    // schedules a function to run later, aka in a wayland idle event.\n    void doLater(const std::function<void()>& fn);\n\n    struct SIdleData {\n        wl_event_source*                   eventSource = nullptr;\n        std::vector<std::function<void()>> fns;\n    };\n\n    struct SReadableWaiter {\n        wl_event_source*               source;\n        Hyprutils::OS::CFileDescriptor fd;\n        std::function<void()>          fn;\n\n        SReadableWaiter(wl_event_source* src, Hyprutils::OS::CFileDescriptor f, std::function<void()> func) : source(src), fd(std::move(f)), fn(std::move(func)) {}\n\n        ~SReadableWaiter() {\n            if (source) {\n                wl_event_source_remove(source);\n                source = nullptr;\n            }\n        }\n\n        // copy\n        SReadableWaiter(const SReadableWaiter&)            = delete;\n        SReadableWaiter& operator=(const SReadableWaiter&) = delete;\n\n        // move\n        SReadableWaiter(SReadableWaiter&& other) noexcept            = default;\n        SReadableWaiter& operator=(SReadableWaiter&& other) noexcept = default;\n    };\n\n    // schedule function to when fd is readable (WL_EVENT_READABLE / POLLIN),\n    // takes ownership of fd\n    void doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function<void()>&& fn);\n    void onFdReadable(SReadableWaiter* waiter);\n    void onFdReadableFail(SReadableWaiter* waiter);\n\n  private:\n    // Manages the event sources after AQ pollFDs change.\n    void syncPollFDs();\n    void nudgeTimers();\n\n    struct SEventSourceData {\n        SP<Aquamarine::SPollFD> pollFD;\n        wl_event_source*        eventSource = nullptr;\n    };\n\n    struct {\n        wl_event_loop*   loop        = nullptr;\n        wl_display*      display     = nullptr;\n        wl_event_source* eventSource = nullptr;\n    } m_wayland;\n\n    struct {\n        std::vector<SP<CEventLoopTimer>> timers;\n        Hyprutils::OS::CFileDescriptor   timerfd;\n        bool                             recalcScheduled = false;\n    } m_timers;\n\n    SIdleData                        m_idle;\n    std::map<int, SEventSourceData>  m_aqEventSources;\n    std::vector<UP<SReadableWaiter>> m_readableWaiters;\n\n    struct {\n        CHyprSignalListener pollFDsChanged;\n    } m_listeners;\n\n    wl_event_source* m_configWatcherInotifySource = nullptr;\n\n    friend class CAsyncDialogBox;\n    friend class CMainLoopExecutor;\n};\n\ninline UP<CEventLoopManager> g_pEventLoopManager;\n"
  },
  {
    "path": "src/managers/eventLoop/EventLoopTimer.cpp",
    "content": "#include \"EventLoopTimer.hpp\"\n#include <limits>\n#include \"EventLoopManager.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n\nCEventLoopTimer::CEventLoopTimer(std::optional<Time::steady_dur> timeout, std::function<void(SP<CEventLoopTimer> self, void* data)> cb_, void* data_) : m_cb(cb_), m_data(data_) {\n\n    if (!timeout.has_value())\n        m_expires.reset();\n    else\n        m_expires = Time::steadyNow() + *timeout;\n}\n\nvoid CEventLoopTimer::updateTimeout(std::optional<Time::steady_dur> timeout) {\n    if (!timeout.has_value()) {\n        m_expires.reset();\n        g_pEventLoopManager->scheduleRecalc();\n        return;\n    }\n\n    m_expires = Time::steadyNow() + *timeout;\n\n    g_pEventLoopManager->scheduleRecalc();\n}\n\nbool CEventLoopTimer::passed() {\n    if (!m_expires.has_value())\n        return false;\n    return Time::steadyNow() > *m_expires;\n}\n\nvoid CEventLoopTimer::cancel() {\n    m_wasCancelled = true;\n    m_expires.reset();\n}\n\nbool CEventLoopTimer::cancelled() {\n    return m_wasCancelled;\n}\n\nvoid CEventLoopTimer::call(SP<CEventLoopTimer> self) {\n    m_expires.reset();\n    m_cb(self, m_data);\n}\n\nfloat CEventLoopTimer::leftUs() {\n    if (!m_expires.has_value())\n        return std::numeric_limits<float>::max();\n\n    return std::chrono::duration_cast<std::chrono::microseconds>(*m_expires - Time::steadyNow()).count();\n}\n\nbool CEventLoopTimer::armed() {\n    return m_expires.has_value();\n}\n"
  },
  {
    "path": "src/managers/eventLoop/EventLoopTimer.hpp",
    "content": "#pragma once\n\n#include <chrono>\n#include <functional>\n#include <optional>\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n\nclass CEventLoopTimer {\n  public:\n    CEventLoopTimer(std::optional<Time::steady_dur> timeout, std::function<void(SP<CEventLoopTimer> self, void* data)> cb_, void* data_);\n\n    // if not specified, disarms.\n    // if specified, arms.\n    void  updateTimeout(std::optional<Time::steady_dur> timeout);\n\n    void  cancel();\n    bool  passed();\n    bool  armed();\n\n    float leftUs();\n\n    bool  cancelled();\n    // resets expires\n    void call(SP<CEventLoopTimer> self);\n\n  private:\n    std::function<void(SP<CEventLoopTimer> self, void* data)> m_cb;\n    void*                                                     m_data = nullptr;\n    std::optional<Time::steady_tp>                            m_expires;\n    bool                                                      m_wasCancelled = false;\n};\n"
  },
  {
    "path": "src/managers/input/IdleInhibitor.cpp",
    "content": "#include \"InputManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../protocols/IdleInhibit.hpp\"\n#include \"../../protocols/IdleNotify.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n\nvoid CInputManager::newIdleInhibitor(std::any inhibitor) {\n    const auto PINHIBIT = m_idleInhibitors.emplace_back(makeUnique<SIdleInhibitor>()).get();\n    PINHIBIT->inhibitor = std::any_cast<SP<CIdleInhibitor>>(inhibitor);\n\n    Log::logger->log(Log::DEBUG, \"New idle inhibitor registered for surface {:x}\", rc<uintptr_t>(PINHIBIT->inhibitor->m_surface.get()));\n\n    PINHIBIT->inhibitor->m_listeners.destroy = PINHIBIT->inhibitor->m_resource->m_events.destroy.listen([this, PINHIBIT] {\n        std::erase_if(m_idleInhibitors, [PINHIBIT](const auto& other) { return other.get() == PINHIBIT; });\n        recheckIdleInhibitorStatus();\n    });\n\n    auto WLSurface = Desktop::View::CWLSurface::fromResource(PINHIBIT->inhibitor->m_surface.lock());\n\n    if (!WLSurface) {\n        Log::logger->log(Log::DEBUG, \"Inhibitor has no HL Surface attached to it, likely meaning it's a non-desktop element. Assuming it's visible.\");\n        PINHIBIT->nonDesktop = true;\n        recheckIdleInhibitorStatus();\n        return;\n    }\n\n    PINHIBIT->surfaceDestroyListener =\n        WLSurface->m_events.destroy.listen([this, PINHIBIT] { std::erase_if(m_idleInhibitors, [PINHIBIT](const auto& other) { return other.get() == PINHIBIT; }); });\n\n    recheckIdleInhibitorStatus();\n}\n\nvoid CInputManager::recheckIdleInhibitorStatus() {\n\n    for (auto const& ii : m_idleInhibitors) {\n        if (ii->nonDesktop) {\n            PROTO::idle->setInhibit(true);\n            return;\n        }\n\n        auto WLSurface = Desktop::View::CWLSurface::fromResource(ii->inhibitor->m_surface.lock());\n\n        if (!WLSurface || !WLSurface->view())\n            continue;\n\n        if (WLSurface->view()->aliveAndVisible()) {\n            PROTO::idle->setInhibit(true);\n            return;\n        }\n    }\n\n    // check manual user-set inhibitors\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (isWindowInhibiting(w)) {\n            PROTO::idle->setInhibit(true);\n            return;\n        }\n    }\n\n    PROTO::idle->setInhibit(false);\n}\n\nbool CInputManager::isWindowInhibiting(const PHLWINDOW& w, bool onlyHl) {\n    if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_ALWAYS)\n        return true;\n\n    if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_FOCUS && g_pCompositor->isWindowActive(w))\n        return true;\n\n    if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_FULLSCREEN && w->isFullscreen() && w->m_workspace && w->m_workspace->isVisible())\n        return true;\n\n    if (onlyHl)\n        return false;\n\n    for (auto const& ii : m_idleInhibitors) {\n        if (ii->nonDesktop || !ii->inhibitor)\n            continue;\n\n        bool isInhibiting = false;\n        w->wlSurface()->resource()->breadthfirst(\n            [&ii](SP<CWLSurfaceResource> surf, const Vector2D& pos, void* data) {\n                if (ii->inhibitor->m_surface != surf)\n                    return;\n\n                auto WLSurface = Desktop::View::CWLSurface::fromResource(surf);\n\n                if (!WLSurface || !WLSurface->view())\n                    return;\n\n                if (WLSurface->view()->aliveAndVisible())\n                    *sc<bool*>(data) = true;\n            },\n            &isInhibiting);\n\n        if (isInhibiting)\n            return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/managers/input/InputManager.cpp",
    "content": "#include \"InputManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include <aquamarine/output/Output.hpp>\n#include <cstdint>\n#include <hyprutils/math/Vector2D.hpp>\n#include <ranges>\n#include <algorithm>\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../config/ConfigManager.hpp\"\n#include \"../../desktop/view/WLSurface.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../protocols/CursorShape.hpp\"\n#include \"../../protocols/IdleInhibit.hpp\"\n#include \"../../protocols/RelativePointer.hpp\"\n#include \"../../protocols/PointerConstraints.hpp\"\n#include \"../../protocols/PointerGestures.hpp\"\n#include \"../../protocols/IdleNotify.hpp\"\n#include \"../../protocols/SessionLock.hpp\"\n#include \"../../protocols/InputMethodV2.hpp\"\n#include \"../../protocols/VirtualKeyboard.hpp\"\n#include \"../../protocols/VirtualPointer.hpp\"\n#include \"../../protocols/LayerShell.hpp\"\n#include \"../../protocols/core/Seat.hpp\"\n#include \"../../protocols/core/DataDevice.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../protocols/XDGShell.hpp\"\n\n#include \"../../devices/Mouse.hpp\"\n#include \"../../devices/VirtualPointer.hpp\"\n#include \"../../devices/Keyboard.hpp\"\n#include \"../../devices/VirtualKeyboard.hpp\"\n#include \"../../devices/TouchDevice.hpp\"\n\n#include \"../../managers/PointerManager.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../managers/KeybindManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../managers/EventManager.hpp\"\n#include \"../../managers/permissions/DynamicPermissionManager.hpp\"\n\n#include \"../../helpers/time/Time.hpp\"\n#include \"../../helpers/MiscFunctions.hpp\"\n\n#include \"../../layout/LayoutManager.hpp\"\n\n#include \"../../event/EventBus.hpp\"\n\n#include \"trackpad/TrackpadGestures.hpp\"\n#include \"../cursor/CursorShapeOverrideController.hpp\"\n\n#include <aquamarine/input/Input.hpp>\n\nCInputManager::CInputManager() {\n    m_listeners.setCursorShape = PROTO::cursorShape->m_events.setShape.listen([this](const CCursorShapeProtocol::SSetShapeEvent& event) {\n        if (!g_pSeatManager->m_state.pointerFocusResource)\n            return;\n\n        if (wl_resource_get_client(event.pMgr->resource()) != g_pSeatManager->m_state.pointerFocusResource->client())\n            return;\n\n        Log::logger->log(Log::DEBUG, \"cursorImage request: shape {} -> {}\", sc<uint32_t>(event.shape), event.shapeName);\n\n        m_cursorSurfaceInfo.wlSurface->unassign();\n        m_cursorSurfaceInfo.vHotspot = {};\n        m_cursorSurfaceInfo.name     = event.shapeName;\n        m_cursorSurfaceInfo.hidden   = false;\n\n        if (!cursorImageUnlocked())\n            return;\n\n        g_pHyprRenderer->setCursorFromName(m_cursorSurfaceInfo.name);\n    });\n\n    m_listeners.newIdleInhibitor = PROTO::idleInhibit->m_events.newIdleInhibitor.listen([this](const auto& data) { newIdleInhibitor(data); });\n\n    m_listeners.newVirtualKeyboard = PROTO::virtualKeyboard->m_events.newKeyboard.listen([this](const auto& keyboard) {\n        newVirtualKeyboard(keyboard);\n        updateCapabilities();\n    });\n\n    m_listeners.newVirtualMouse = PROTO::virtualPointer->m_events.newPointer.listen([this](const auto& mouse) {\n        newVirtualMouse(mouse);\n        updateCapabilities();\n    });\n\n    m_listeners.setCursor = g_pSeatManager->m_events.setCursor.listen([this](const auto& event) { processMouseRequest(event); });\n\n    m_listeners.overrideChanged = Cursor::overrideController->m_events.overrideChanged.listen([this](const std::string& shape) {\n        if (shape.empty()) {\n            m_cursorImageOverridden = false;\n            restoreCursorIconToApp();\n            return;\n        }\n\n        m_cursorImageOverridden = true;\n        g_pHyprRenderer->setCursorFromName(shape);\n    });\n\n    m_cursorSurfaceInfo.wlSurface = Desktop::View::CWLSurface::create();\n}\n\nCInputManager::~CInputManager() {\n    m_constraints.clear();\n    m_keyboards.clear();\n    m_pointers.clear();\n    m_touches.clear();\n    m_tablets.clear();\n    m_tabletTools.clear();\n    m_tabletPads.clear();\n    m_idleInhibitors.clear();\n    m_switches.clear();\n}\n\nvoid CInputManager::onMouseMoved(IPointer::SMotionEvent e) {\n    static auto PNOACCEL = CConfigValue<Hyprlang::INT>(\"input:force_no_accel\");\n\n    Vector2D    delta   = e.delta;\n    Vector2D    unaccel = e.unaccel;\n\n    if (e.device) {\n        if (e.device->m_isTouchpad) {\n            if (e.device->m_flipX) {\n                delta.x   = -delta.x;\n                unaccel.x = -unaccel.x;\n            }\n            if (e.device->m_flipY) {\n                delta.y   = -delta.y;\n                unaccel.y = -unaccel.y;\n            }\n        }\n    }\n\n    const auto DELTA = *PNOACCEL == 1 ? unaccel : delta;\n\n    if (e.mouse)\n        recheckMouseWarpOnMouseInput();\n\n    PROTO::relativePointer->sendRelativeMotion(sc<uint64_t>(e.timeMs) * 1000, delta, unaccel);\n    g_pPointerManager->move(DELTA);\n\n    mouseMoveUnified(e.timeMs, false, e.mouse);\n\n    m_lastCursorMovement.reset();\n\n    m_lastInputTouch  = false;\n    m_lastInputTablet = false;\n\n    if (e.mouse)\n        m_lastMousePos = getMouseCoordsInternal();\n\n    g_pSeatManager->sendPointerFrame();\n}\n\nvoid CInputManager::onMouseWarp(IPointer::SMotionAbsoluteEvent e) {\n    g_pPointerManager->warpAbsolute(e.absolute, e.device);\n\n    mouseMoveUnified(e.timeMs);\n\n    m_lastCursorMovement.reset();\n\n    m_lastInputTouch  = false;\n    m_lastInputTablet = false;\n}\n\nvoid CInputManager::simulateMouseMovement() {\n    m_lastCursorPosFloored = m_lastCursorPosFloored - Vector2D(1, 1); // hack: force the mouseMoveUnified to report without making this a refocus.\n    mouseMoveUnified(Time::millis(Time::steadyNow()));\n}\n\nvoid CInputManager::sendMotionEventsToFocused() {\n    if (!Desktop::focusState()->surface() || isConstrained())\n        return;\n\n    const auto SURF = Desktop::focusState()->surface();\n\n    if (!SURF)\n        return;\n\n    const auto HLSurf = Desktop::View::CWLSurface::fromResource(SURF);\n\n    if (!HLSurf || !HLSurf->view())\n        return;\n\n    const auto VIEW = HLSurf->view();\n\n    if (!VIEW->aliveAndVisible())\n        return;\n\n    const auto BOX = HLSurf->getSurfaceBoxGlobal();\n\n    if (!BOX)\n        return;\n\n    m_emptyFocusCursorSet = false;\n\n    g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), getMouseCoordsInternal().floor() - BOX->pos());\n}\n\nvoid CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, std::optional<Vector2D> overridePos) {\n    m_lastInputMouse = mouse;\n\n    if (!g_pCompositor->m_readyToProcess || g_pCompositor->m_isShuttingDown || g_pCompositor->m_unsafeState)\n        return;\n\n    Vector2D const mouseCoords        = overridePos.value_or(getMouseCoordsInternal());\n    auto const     MOUSECOORDSFLOORED = mouseCoords.floor();\n\n    if (MOUSECOORDSFLOORED == m_lastCursorPosFloored && !refocus)\n        return;\n\n    static auto PFOLLOWMOUSE          = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n    static auto PFOLLOWMOUSETHRESHOLD = CConfigValue<Hyprlang::FLOAT>(\"input:follow_mouse_threshold\");\n    static auto PMOUSEREFOCUS         = CConfigValue<Hyprlang::INT>(\"input:mouse_refocus\");\n    static auto PFOLLOWONDND          = CConfigValue<Hyprlang::INT>(\"misc:always_follow_on_dnd\");\n    static auto PFLOATBEHAVIOR        = CConfigValue<Hyprlang::INT>(\"input:float_switch_override_focus\");\n    static auto PMOUSEFOCUSMON        = CConfigValue<Hyprlang::INT>(\"misc:mouse_move_focuses_monitor\");\n    static auto PRESIZEONBORDER       = CConfigValue<Hyprlang::INT>(\"general:resize_on_border\");\n    static auto PRESIZECURSORICON     = CConfigValue<Hyprlang::INT>(\"general:hover_icon_on_border\");\n\n    const auto  FOLLOWMOUSE = *PFOLLOWONDND && PROTO::data->dndActive() ? 1 : *PFOLLOWMOUSE;\n\n    if (FOLLOWMOUSE == 1 && m_lastCursorMovement.getSeconds() < 0.5)\n        m_mousePosDelta += MOUSECOORDSFLOORED.distance(m_lastCursorPosFloored);\n    else\n        m_mousePosDelta = 0;\n\n    m_foundSurfaceToFocus.reset();\n    m_foundLSToFocus.reset();\n    m_foundWindowToFocus.reset();\n    SP<CWLSurfaceResource> foundSurface;\n    Vector2D               surfaceCoords;\n    Vector2D               surfacePos = Vector2D(-1337, -1337);\n    PHLWINDOW              pFoundWindow;\n    PHLLS                  pFoundLayerSurface;\n    const auto             FOCUS_REASON = refocus ? Desktop::FOCUS_REASON_CLICK : Desktop::FOCUS_REASON_FFM;\n\n    Event::SCallbackInfo   info;\n    Event::bus()->m_events.input.mouse.move.emit(MOUSECOORDSFLOORED, info);\n    if (info.cancelled)\n        return;\n\n    m_lastCursorPosFloored = MOUSECOORDSFLOORED;\n\n    // use mouseCoords specifically in case touch sent overridePos, otherwise touch doesn't work on non-focused monitor\n    const auto PMONITOR = isLocked() && Desktop::focusState()->monitor() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromVector(mouseCoords);\n\n    // this can happen if there are no displays hooked up to Hyprland\n    if (PMONITOR == nullptr)\n        return;\n\n    if (PMONITOR->m_cursorZoom->value() != 1.f)\n        g_pHyprRenderer->damageMonitor(PMONITOR);\n\n    bool skipFrameSchedule = PMONITOR->shouldSkipScheduleFrameOnMouseEvent();\n\n    if (!PMONITOR->m_solitaryClient.lock() && g_pHyprRenderer->shouldRenderCursor() && g_pPointerManager->softwareLockedFor(PMONITOR->m_self.lock()) && !skipFrameSchedule)\n        g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE);\n\n    // constraints\n    if (mouse && !g_pSeatManager->m_mouse.expired() && isConstrained()) {\n        const auto SURF       = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface());\n        const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr;\n\n        if (CONSTRAINT) {\n            if (CONSTRAINT->isLocked()) {\n                const auto HINT = CONSTRAINT->logicPositionHint();\n                g_pCompositor->warpCursorTo(HINT, true);\n            } else {\n                const auto RG           = CONSTRAINT->logicConstraintRegion();\n                const auto CLOSEST      = RG.closestPoint(mouseCoords);\n                const auto BOX          = SURF->getSurfaceBoxGlobal();\n                const auto WINDOW       = Desktop::View::CWindow::fromView(SURF->view());\n                const auto CLOSESTLOCAL = (CLOSEST - (BOX.has_value() ? BOX->pos() : Vector2D{})) * (WINDOW ? WINDOW->m_X11SurfaceScaledBy : 1.0);\n\n                g_pCompositor->warpCursorTo(CLOSEST, true);\n                g_pSeatManager->sendPointerMotion(time, CLOSESTLOCAL);\n                PROTO::relativePointer->sendRelativeMotion(sc<uint64_t>(time) * 1000, {}, {});\n            }\n\n            return;\n\n        } else\n            Log::logger->log(Log::ERR, \"BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}\", rc<uintptr_t>(SURF.get()),\n                             rc<uintptr_t>(CONSTRAINT.get()));\n    }\n\n    if (PMONITOR != Desktop::focusState()->monitor() && (*PMOUSEFOCUSMON || refocus) && m_forcedFocus.expired())\n        Desktop::focusState()->rawMonitorFocus(PMONITOR);\n\n    // check for windows that have focus priority like our permission popups\n    pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::FOCUS_PRIORITY);\n    if (pFoundWindow)\n        foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords);\n\n    if (!foundSurface && g_pSessionLockManager->isSessionLocked()) {\n\n        // set keyboard focus on session lock surface regardless of layers\n        const auto PSESSIONLOCKSURFACE = g_pSessionLockManager->getSessionLockSurfaceForMonitor(PMONITOR->m_id);\n        const auto foundLockSurface    = PSESSIONLOCKSURFACE ? PSESSIONLOCKSURFACE->surface->surface() : nullptr;\n\n        Desktop::focusState()->rawSurfaceFocus(foundLockSurface);\n\n        // search for interactable abovelock surfaces for pointer focus, or use session lock surface if not found\n        for (auto& lsl : PMONITOR->m_layerSurfaceLayers | std::views::reverse) {\n            foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &lsl, &surfaceCoords, &pFoundLayerSurface, true);\n\n            if (foundSurface)\n                break;\n        }\n\n        if (!foundSurface) {\n            surfaceCoords = mouseCoords - PMONITOR->m_position;\n            foundSurface  = foundLockSurface;\n        }\n\n        if (refocus) {\n            m_foundLSToFocus      = pFoundLayerSurface;\n            m_foundWindowToFocus  = pFoundWindow;\n            m_foundSurfaceToFocus = foundSurface;\n        }\n\n        g_pSeatManager->setPointerFocus(foundSurface, surfaceCoords);\n        g_pSeatManager->sendPointerMotion(time, surfaceCoords);\n\n        return;\n    }\n\n    PHLWINDOW forcedFocus = m_forcedFocus.lock();\n\n    if (!forcedFocus)\n        forcedFocus = g_pCompositor->getForceFocus();\n\n    if (forcedFocus && !foundSurface) {\n        pFoundWindow = forcedFocus;\n        surfacePos   = pFoundWindow->m_realPosition->value();\n        foundSurface = pFoundWindow->wlSurface()->resource();\n    }\n\n    // if we are holding a pointer button,\n    // and we're not dnd-ing, don't refocus. Keep focus on last surface.\n    if (mouse && !PROTO::data->dndActive() && !m_currentlyHeldButtons.empty() && Desktop::focusState()->surface() && Desktop::focusState()->surface()->m_mapped &&\n        g_pSeatManager->m_state.pointerFocus && !m_hardInput) {\n        foundSurface = g_pSeatManager->m_state.pointerFocus.lock();\n\n        // IME popups aren't desktop-like elements\n        // TODO: make them.\n        CInputPopup* foundPopup = m_relay.popupFromSurface(foundSurface);\n        if (foundPopup) {\n            surfacePos             = foundPopup->globalBox().pos();\n            m_focusHeldByButtons   = true;\n            m_refocusHeldByButtons = refocus;\n        } else {\n            auto HLSurface = Desktop::View::CWLSurface::fromResource(foundSurface);\n\n            if (HLSurface) {\n                const auto BOX = HLSurface->getSurfaceBoxGlobal();\n\n                if (BOX) {\n                    const auto PWINDOW = Desktop::View::CWindow::fromView(HLSurface->view());\n                    const auto LS      = Desktop::View::CLayerSurface::fromView(HLSurface->view());\n                    surfacePos         = BOX->pos();\n\n                    if (PWINDOW)\n                        pFoundWindow = PWINDOW;\n                    else if (LS)\n                        pFoundLayerSurface = LS;\n\n                } else // reset foundSurface, find one normally\n                    foundSurface = nullptr;\n            } else // reset foundSurface, find one normally\n                foundSurface = nullptr;\n        }\n    }\n\n    g_layoutManager->moveMouse(getMouseCoordsInternal());\n\n    // forced above all\n    if (!g_pInputManager->m_exclusiveLSes.empty()) {\n        if (!foundSurface)\n            foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &g_pInputManager->m_exclusiveLSes, &surfaceCoords, &pFoundLayerSurface);\n\n        if (!foundSurface) {\n            foundSurface = (*g_pInputManager->m_exclusiveLSes.begin())->wlSurface()->resource();\n            surfacePos   = (*g_pInputManager->m_exclusiveLSes.begin())->m_realPosition->goal();\n        }\n    }\n\n    if (!foundSurface)\n        foundSurface = g_pCompositor->vectorToLayerPopupSurface(mouseCoords, PMONITOR, &surfaceCoords, &pFoundLayerSurface);\n\n    // overlays are above fullscreen\n    if (!foundSurface)\n        foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &surfaceCoords, &pFoundLayerSurface);\n\n    // also IME popups\n    if (!foundSurface) {\n        auto popup = g_pInputManager->m_relay.popupFromCoords(mouseCoords);\n        if (popup) {\n            foundSurface = popup->getSurface();\n            surfacePos   = popup->globalBox().pos();\n        }\n    }\n\n    // also top layers\n    if (!foundSurface)\n        foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &surfaceCoords, &pFoundLayerSurface);\n\n    // then, we check if the workspace doesn't have a fullscreen window\n    const auto PWORKSPACE   = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace;\n    const auto PWINDOWIDEAL = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n    if (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) {\n        const auto IS_LS_UNFOCUSABLE = pFoundLayerSurface &&\n            (pFoundLayerSurface->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP ||\n             (pFoundLayerSurface->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP && !pFoundLayerSurface->m_aboveFullscreen));\n\n        if (IS_LS_UNFOCUSABLE) {\n            foundSurface       = nullptr;\n            pFoundLayerSurface = nullptr;\n\n            pFoundWindow = PWORKSPACE->getFullscreenWindow();\n\n            if (!pFoundWindow) {\n                // what the fuck, somehow happens occasionally??\n                PWORKSPACE->m_hasFullscreenWindow = false;\n                return;\n            }\n\n            if (PWINDOWIDEAL &&\n                ((PWINDOWIDEAL->m_isFloating && (PWINDOWIDEAL->m_createdOverFullscreen || PWINDOWIDEAL->m_pinned)) /* floating over fullscreen or pinned */\n                 || (PMONITOR->m_activeSpecialWorkspace == PWINDOWIDEAL->m_workspace) /* on an open special workspace */))\n                pFoundWindow = PWINDOWIDEAL;\n\n            if (!pFoundWindow->m_isX11) {\n                foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords);\n                surfacePos   = Vector2D(-1337, -1337);\n            } else {\n                foundSurface = pFoundWindow->wlSurface()->resource();\n                surfacePos   = pFoundWindow->m_realPosition->value();\n            }\n        }\n    }\n\n    // then windows\n    if (!foundSurface) {\n        if (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_MAXIMIZED) {\n            if (!foundSurface) {\n                if (PMONITOR->m_activeSpecialWorkspace) {\n                    if (pFoundWindow != PWINDOWIDEAL)\n                        pFoundWindow =\n                            g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n\n                    if (pFoundWindow && !pFoundWindow->onSpecialWorkspace()) {\n                        pFoundWindow = PWORKSPACE->getFullscreenWindow();\n                    }\n                } else {\n                    // if we have a maximized window, allow focusing on a bar or something if in reserved area.\n                    if (g_pCompositor->isPointOnReservedArea(mouseCoords, PMONITOR)) {\n                        foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &surfaceCoords,\n                                                                           &pFoundLayerSurface);\n                    }\n\n                    if (!foundSurface) {\n                        if (pFoundWindow != PWINDOWIDEAL)\n                            pFoundWindow =\n                                g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n\n                        if (!(pFoundWindow && (pFoundWindow->m_isFloating && (pFoundWindow->m_createdOverFullscreen || pFoundWindow->m_pinned))))\n                            pFoundWindow = PWORKSPACE->getFullscreenWindow();\n                    }\n                }\n            }\n\n        } else {\n            if (pFoundWindow != PWINDOWIDEAL)\n                pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n        }\n\n        if (pFoundWindow) {\n            if (!pFoundWindow->m_isX11) {\n                foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords);\n                if (!foundSurface) {\n                    foundSurface = pFoundWindow->wlSurface()->resource();\n                    surfacePos   = pFoundWindow->m_realPosition->value();\n                }\n            } else {\n                foundSurface = pFoundWindow->wlSurface()->resource();\n                surfacePos   = pFoundWindow->m_realPosition->value();\n            }\n        }\n    }\n\n    // then surfaces below\n    if (!foundSurface)\n        foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &surfaceCoords, &pFoundLayerSurface);\n\n    if (!foundSurface)\n        foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &surfaceCoords, &pFoundLayerSurface);\n\n    if (g_pPointerManager->softwareLockedFor(PMONITOR->m_self.lock()) > 0 && !skipFrameSchedule)\n        g_pCompositor->scheduleFrameForMonitor(Desktop::focusState()->monitor(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE);\n\n    // FIXME: This will be disabled during DnD operations because we do not exactly follow the spec\n    // xdg-popup grabs should be keyboard-only, while they are absolute in our case...\n    if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(foundSurface) && !PROTO::data->dndActive()) {\n        if (m_hardInput || refocus) {\n            g_pSeatManager->setGrab(nullptr);\n            return; // setGrab will refocus\n        } else {\n            // we need to grab the last surface.\n            foundSurface = g_pSeatManager->m_state.pointerFocus.lock();\n\n            auto HLSurface = Desktop::View::CWLSurface::fromResource(foundSurface);\n\n            if (HLSurface) {\n                const auto BOX = HLSurface->getSurfaceBoxGlobal();\n\n                if (BOX.has_value())\n                    surfacePos = BOX->pos();\n            }\n        }\n    }\n\n    if (!foundSurface) {\n        if (!m_emptyFocusCursorSet) {\n            g_pHyprRenderer->setCursorFromName(\"left_ptr\");\n            m_emptyFocusCursorSet = true;\n        }\n\n        g_pSeatManager->setPointerFocus(nullptr, {});\n\n        if (refocus || !Desktop::focusState()->window()) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too!\n            Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON);\n\n        return;\n    }\n\n    m_emptyFocusCursorSet = false;\n\n    Vector2D surfaceLocal = surfacePos == Vector2D(-1337, -1337) ? surfaceCoords : mouseCoords - surfacePos;\n\n    if (pFoundWindow && pFoundWindow->m_isX11) // for x11 force scale zero\n        surfaceLocal = surfaceLocal * pFoundWindow->m_X11SurfaceScaledBy;\n\n    bool allowKeyboardRefocus = true;\n\n    if (!refocus && Desktop::focusState()->surface()) {\n        const auto PLS = g_pCompositor->getLayerSurfaceFromSurface(Desktop::focusState()->surface());\n\n        if (PLS && PLS->m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE)\n            allowKeyboardRefocus = false;\n    }\n\n    // set the values for use\n    if (refocus) {\n        m_foundLSToFocus      = pFoundLayerSurface;\n        m_foundWindowToFocus  = pFoundWindow;\n        m_foundSurfaceToFocus = foundSurface;\n    }\n\n    if (g_layoutManager->dragController()->target() && pFoundWindow != g_layoutManager->dragController()->target()) {\n        g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);\n        return;\n    }\n\n    if (pFoundWindow && foundSurface == pFoundWindow->wlSurface()->resource() && !m_cursorImageOverridden) {\n        const auto BOX = pFoundWindow->getWindowMainSurfaceBox();\n        if (VECNOTINRECT(mouseCoords, BOX.x, BOX.y, BOX.x + BOX.width, BOX.y + BOX.height))\n            g_pHyprRenderer->setCursorFromName(\"left_ptr\");\n        else\n            restoreCursorIconToApp();\n    }\n\n    if (pFoundWindow) {\n        // change cursor icon if hovering over border\n        if (*PRESIZEONBORDER && *PRESIZECURSORICON) {\n            if (!pFoundWindow->isFullscreen() && !pFoundWindow->hasPopupAt(mouseCoords))\n                setCursorIconOnBorder(pFoundWindow);\n            else if (m_borderIconDirection != BORDERICON_NONE) {\n                m_borderIconDirection = BORDERICON_NONE;\n                Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE);\n            }\n        } else if (m_borderIconDirection != BORDERICON_NONE) {\n            m_borderIconDirection = BORDERICON_NONE;\n            Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE);\n        }\n\n        if (FOLLOWMOUSE != 1 && !refocus) {\n            if (pFoundWindow != Desktop::focusState()->window() && Desktop::focusState()->window() &&\n                ((pFoundWindow->m_isFloating && *PFLOATBEHAVIOR == 2) || (Desktop::focusState()->window()->m_isFloating != pFoundWindow->m_isFloating && *PFLOATBEHAVIOR != 0))) {\n                // enter if change floating style\n                if (FOLLOWMOUSE != 3 && allowKeyboardRefocus)\n                    Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface);\n                g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);\n            } else if (FOLLOWMOUSE == 2 || FOLLOWMOUSE == 3)\n                g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);\n\n            if (pFoundWindow == Desktop::focusState()->window())\n                g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);\n\n            if (FOLLOWMOUSE != 0 || pFoundWindow == Desktop::focusState()->window())\n                g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);\n\n            if (g_pSeatManager->m_state.pointerFocus == foundSurface)\n                g_pSeatManager->sendPointerMotion(time, surfaceLocal);\n\n            m_lastFocusOnLS = false;\n            return; // don't enter any new surfaces\n        } else {\n            if (allowKeyboardRefocus && ((FOLLOWMOUSE != 3 && (*PMOUSEREFOCUS || m_lastMouseFocus.lock() != pFoundWindow)) || refocus)) {\n                if (m_lastMouseFocus.lock() != pFoundWindow || Desktop::focusState()->window() != pFoundWindow || Desktop::focusState()->surface() != foundSurface || refocus) {\n                    m_lastMouseFocus = pFoundWindow;\n\n                    // TODO: this looks wrong. When over a popup, it constantly is switching.\n                    // Temp fix until that's figured out. Otherwise spams windowrule lookups and other shit.\n                    if (m_lastMouseFocus.lock() != pFoundWindow || Desktop::focusState()->window() != pFoundWindow) {\n                        if (m_mousePosDelta > *PFOLLOWMOUSETHRESHOLD || refocus) {\n                            const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_ruleApplicator->noFollowMouse().valueOrDefault();\n\n                            if (refocus || !hasNoFollowMouse)\n                                Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface);\n                        }\n                    } else\n                        Desktop::focusState()->rawSurfaceFocus(foundSurface, pFoundWindow);\n                }\n            }\n        }\n\n        if (g_pSeatManager->m_state.keyboardFocus == nullptr)\n            Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface);\n\n        m_lastFocusOnLS = false;\n    } else {\n        if (*PRESIZEONBORDER && *PRESIZECURSORICON && m_borderIconDirection != BORDERICON_NONE) {\n            m_borderIconDirection = BORDERICON_NONE;\n            Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE);\n        }\n\n        if (pFoundLayerSurface && (pFoundLayerSurface->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) && FOLLOWMOUSE != 3 &&\n            (allowKeyboardRefocus || pFoundLayerSurface->m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE)) {\n            Desktop::focusState()->rawSurfaceFocus(foundSurface);\n        }\n\n        if (pFoundLayerSurface)\n            m_lastFocusOnLS = true;\n    }\n\n    if (mouse) {\n        g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal);\n        g_pSeatManager->sendPointerMotion(time, surfaceLocal);\n    }\n}\n\nvoid CInputManager::onMouseButton(IPointer::SButtonEvent e, SP<IPointer> mouse) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.mouse.button.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    if (e.mouse)\n        recheckMouseWarpOnMouseInput();\n\n    m_lastCursorMovement.reset();\n\n    if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) {\n        m_currentlyHeldButtons.push_back(e.button);\n    } else {\n        if (std::ranges::find_if(m_currentlyHeldButtons, [&](const auto& other) { return other == e.button; }) == m_currentlyHeldButtons.end())\n            return;\n        std::erase_if(m_currentlyHeldButtons, [&](const auto& other) { return other == e.button; });\n    }\n\n    switch (m_clickBehavior) {\n        case CLICKMODE_DEFAULT: processMouseDownNormal(e, mouse); break;\n        case CLICKMODE_KILL: processMouseDownKill(e); break;\n        default: break;\n    }\n\n    if (m_focusHeldByButtons && m_currentlyHeldButtons.empty() && e.state == WL_POINTER_BUTTON_STATE_RELEASED) {\n        if (m_refocusHeldByButtons)\n            refocus();\n        else\n            simulateMouseMovement();\n\n        m_focusHeldByButtons   = false;\n        m_refocusHeldByButtons = false;\n    }\n\n    g_pSeatManager->sendPointerFrame();\n}\n\nvoid CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) {\n    Log::logger->log(Log::DEBUG, \"cursorImage request: surface {:x}\", rc<uintptr_t>(event.surf.get()));\n\n    if (event.surf != m_cursorSurfaceInfo.wlSurface->resource()) {\n        m_cursorSurfaceInfo.wlSurface->unassign();\n\n        if (event.surf)\n            m_cursorSurfaceInfo.wlSurface->assign(event.surf);\n    }\n\n    if (event.surf) {\n        m_cursorSurfaceInfo.vHotspot = event.hotspot;\n        m_cursorSurfaceInfo.hidden   = false;\n    } else {\n        m_cursorSurfaceInfo.vHotspot = {};\n        m_cursorSurfaceInfo.hidden   = true;\n    }\n\n    m_cursorSurfaceInfo.name = \"\";\n\n    if (!cursorImageUnlocked())\n        return;\n\n    g_pHyprRenderer->setCursorSurface(m_cursorSurfaceInfo.wlSurface, event.hotspot.x, event.hotspot.y);\n}\n\nvoid CInputManager::restoreCursorIconToApp() {\n    if (m_cursorSurfaceInfo.hidden) {\n        g_pHyprRenderer->setCursorSurface(nullptr, 0, 0);\n        return;\n    }\n\n    if (m_cursorSurfaceInfo.name.empty()) {\n        if (m_cursorSurfaceInfo.wlSurface->exists())\n            g_pHyprRenderer->setCursorSurface(m_cursorSurfaceInfo.wlSurface, m_cursorSurfaceInfo.vHotspot.x, m_cursorSurfaceInfo.vHotspot.y);\n    } else\n        g_pHyprRenderer->setCursorFromName(m_cursorSurfaceInfo.name);\n}\n\nbool CInputManager::cursorImageUnlocked() {\n    return !m_cursorImageOverridden;\n}\n\neClickBehaviorMode CInputManager::getClickMode() {\n    return m_clickBehavior;\n}\n\nvoid CInputManager::setClickMode(eClickBehaviorMode mode) {\n    switch (mode) {\n        case CLICKMODE_DEFAULT:\n            Log::logger->log(Log::DEBUG, \"SetClickMode: DEFAULT\");\n            m_clickBehavior = CLICKMODE_DEFAULT;\n            g_pHyprRenderer->setCursorFromName(\"left_ptr\", true);\n            break;\n\n        case CLICKMODE_KILL:\n            Log::logger->log(Log::DEBUG, \"SetClickMode: KILL\");\n            m_clickBehavior = CLICKMODE_KILL;\n\n            // remove constraints\n            g_pInputManager->unconstrainMouse();\n            refocus();\n\n            // set cursor\n            Cursor::overrideController->setOverride(\"crosshair\", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n            break;\n        default: break;\n    }\n}\n\nvoid CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e, SP<IPointer> mouse) {\n\n    // notify the keybind manager\n    static auto PPASSMOUSE        = CConfigValue<Hyprlang::INT>(\"binds:pass_mouse_when_bound\");\n    const auto  PASS              = g_pKeybindManager->onMouseEvent(e, mouse);\n    static auto PFOLLOWMOUSE      = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n    static auto PRESIZEONBORDER   = CConfigValue<Hyprlang::INT>(\"general:resize_on_border\");\n    static auto PBORDERSIZE       = CConfigValue<Hyprlang::INT>(\"general:border_size\");\n    static auto PBORDERGRABEXTEND = CConfigValue<Hyprlang::INT>(\"general:extend_border_grab_area\");\n    const auto  BORDER_GRAB_AREA  = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0;\n\n    if (!PASS && !*PPASSMOUSE)\n        return;\n\n    const auto mouseCoords = g_pInputManager->getMouseCoordsInternal();\n    const auto w           = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::ALLOW_FLOATING | Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS);\n\n    if (w && !m_lastFocusOnLS && !g_pSessionLockManager->isSessionLocked() && w->checkInputOnDecos(INPUT_TYPE_BUTTON, mouseCoords, e))\n        return;\n\n    // clicking on border triggers resize\n    // TODO detect click on LS properly\n    if (*PRESIZEONBORDER && !g_pSessionLockManager->isSessionLocked() && !m_lastFocusOnLS && e.state == WL_POINTER_BUTTON_STATE_PRESSED && (!w || !w->isX11OverrideRedirect())) {\n        if (w && !w->isFullscreen()) {\n            const CBox real = {w->m_realPosition->value().x, w->m_realPosition->value().y, w->m_realSize->value().x, w->m_realSize->value().y};\n            const CBox grab = {real.x - BORDER_GRAB_AREA, real.y - BORDER_GRAB_AREA, real.width + 2 * BORDER_GRAB_AREA, real.height + 2 * BORDER_GRAB_AREA};\n\n            if ((grab.containsPoint(mouseCoords) && (!real.containsPoint(mouseCoords) || w->isInCurvedCorner(mouseCoords.x, mouseCoords.y))) && !w->hasPopupAt(mouseCoords)) {\n                g_pKeybindManager->resizeWithBorder(e);\n                return;\n            }\n        }\n    }\n\n    switch (e.state) {\n        case WL_POINTER_BUTTON_STATE_PRESSED: {\n            if (*PFOLLOWMOUSE == 3) // don't refocus on full loose\n                break;\n\n            if ((g_pSeatManager->m_mouse.expired() || !isConstrained()) /* No constraints */\n                && (w && Desktop::focusState()->window() != w) /* window should change */) {\n                // a bit hacky\n                // if we only pressed one button, allow us to refocus. m_lCurrentlyHeldButtons.size() > 0 will stick the focus\n                if (m_currentlyHeldButtons.size() == 1) {\n                    const auto COPY = m_currentlyHeldButtons;\n                    m_currentlyHeldButtons.clear();\n                    refocus();\n                    m_currentlyHeldButtons = COPY;\n                } else\n                    refocus();\n            }\n\n            // if clicked on a floating window make it top\n            if (!g_pSeatManager->m_state.pointerFocus)\n                break;\n\n            auto HLSurf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock());\n\n            // pointerFocus can target a surface without a Desktop::View (e.g. IME popups), so view() may be null.\n            const auto PVIEW = HLSurf ? HLSurf->view() : nullptr;\n            if (PVIEW && PVIEW->type() == Desktop::View::VIEW_TYPE_WINDOW)\n                g_pCompositor->changeWindowZOrder(dynamicPointerCast<Desktop::View::CWindow>(PVIEW), true);\n\n            break;\n        }\n        case WL_POINTER_BUTTON_STATE_RELEASED: break;\n    }\n\n    // notify app if we didn't handle it\n    g_pSeatManager->sendPointerButton(e.timeMs, e.button, e.state);\n\n    if (const auto PMON = g_pCompositor->getMonitorFromVector(mouseCoords); PMON != Desktop::focusState()->monitor() && PMON)\n        Desktop::focusState()->rawMonitorFocus(PMON);\n\n    if (g_pSeatManager->m_seatGrab && e.state == WL_POINTER_BUTTON_STATE_PRESSED) {\n        m_hardInput = true;\n        simulateMouseMovement();\n        m_hardInput = false;\n    }\n}\n\nvoid CInputManager::processMouseDownKill(const IPointer::SButtonEvent& e) {\n    switch (e.state) {\n        case WL_POINTER_BUTTON_STATE_PRESSED: {\n            const auto PWINDOW =\n                g_pCompositor->vectorToWindowUnified(getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n\n            if (!PWINDOW) {\n                Log::logger->log(Log::ERR, \"Cannot kill invalid window!\");\n                break;\n            }\n\n            g_pEventManager->postEvent(SHyprIPCEvent({.event = \"kill\", .data = std::format(\"{:x}\", rc<uintptr_t>(PWINDOW.m_data))}));\n            Event::bus()->m_events.window.kill.emit(PWINDOW);\n\n            // kill the mf\n            kill(PWINDOW->getPID(), SIGKILL);\n            break;\n        }\n        case WL_POINTER_BUTTON_STATE_RELEASED: break;\n        default: break;\n    }\n\n    // reset click behavior mode\n    m_clickBehavior = CLICKMODE_DEFAULT;\n    Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);\n}\n\nvoid CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP<IPointer> pointer) {\n    static auto POFFWINDOWAXIS        = CConfigValue<Hyprlang::INT>(\"input:off_window_axis_events\");\n    static auto PINPUTSCROLLFACTOR    = CConfigValue<Hyprlang::FLOAT>(\"input:scroll_factor\");\n    static auto PTOUCHPADSCROLLFACTOR = CConfigValue<Hyprlang::FLOAT>(\"input:touchpad:scroll_factor\");\n    static auto PEMULATEDISCRETE      = CConfigValue<Hyprlang::INT>(\"input:emulate_discrete_scroll\");\n    static auto PFOLLOWMOUSE          = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n\n    const bool  ISTOUCHPADSCROLL = *PTOUCHPADSCROLLFACTOR <= 0.f || e.source == WL_POINTER_AXIS_SOURCE_FINGER;\n    auto        factor           = ISTOUCHPADSCROLL ? *PTOUCHPADSCROLLFACTOR : *PINPUTSCROLLFACTOR;\n\n    if (pointer && pointer->m_scrollFactor.has_value())\n        factor = *pointer->m_scrollFactor;\n\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.mouse.axis.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    if (e.mouse)\n        recheckMouseWarpOnMouseInput();\n\n    bool passEvent = g_pKeybindManager->onAxisEvent(e, pointer);\n\n    if (!passEvent)\n        return;\n\n    if (!m_lastFocusOnLS) {\n        const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();\n        const auto PWINDOW     = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);\n\n        if (PWINDOW) {\n            if (PWINDOW->checkInputOnDecos(INPUT_TYPE_AXIS, MOUSECOORDS, e))\n                return;\n\n            if (*POFFWINDOWAXIS != 1) {\n                const auto BOX = PWINDOW->getWindowMainSurfaceBox();\n\n                if (!BOX.containsPoint(MOUSECOORDS) && !PWINDOW->hasPopupAt(MOUSECOORDS)) {\n                    if (*POFFWINDOWAXIS == 0)\n                        return;\n\n                    const auto TEMPCURX = std::clamp(MOUSECOORDS.x, BOX.x, BOX.x + BOX.w - 1);\n                    const auto TEMPCURY = std::clamp(MOUSECOORDS.y, BOX.y, BOX.y + BOX.h - 1);\n\n                    if (*POFFWINDOWAXIS == 3)\n                        g_pCompositor->warpCursorTo({TEMPCURX, TEMPCURY}, true);\n\n                    g_pSeatManager->sendPointerMotion(e.timeMs, Vector2D{TEMPCURX, TEMPCURY} - BOX.pos());\n                    g_pSeatManager->sendPointerFrame();\n                }\n            }\n\n            if (g_pSeatManager->m_state.pointerFocus) {\n                const auto PCURRWINDOW = g_pCompositor->getWindowFromSurface(g_pSeatManager->m_state.pointerFocus.lock());\n\n                if (*PFOLLOWMOUSE == 1 && PCURRWINDOW && PWINDOW != PCURRWINDOW)\n                    simulateMouseMovement();\n            }\n\n            if (!ISTOUCHPADSCROLL && PWINDOW->isScrollMouseOverridden())\n                factor = PWINDOW->getScrollMouse();\n            else if (ISTOUCHPADSCROLL && PWINDOW->isScrollTouchpadOverridden())\n                factor = PWINDOW->getScrollTouchpad();\n        }\n    }\n\n    double discrete = (e.deltaDiscrete != 0) ? (factor * e.deltaDiscrete / std::abs(e.deltaDiscrete)) : 0;\n    double delta    = e.delta * factor;\n\n    if (e.source == 0) {\n        // if an application supports v120, it should ignore discrete anyways\n        if ((*PEMULATEDISCRETE >= 1 && std::abs(e.deltaDiscrete) != 120) || *PEMULATEDISCRETE >= 2) {\n\n            const int interval = factor != 0 ? std::round(120 * (1 / factor)) : 120;\n\n            // reset the accumulator when timeout is reached or direction/axis has changed\n            if (std::signbit(e.deltaDiscrete) != m_scrollWheelState.lastEventSign || e.axis != m_scrollWheelState.lastEventAxis ||\n                e.timeMs - m_scrollWheelState.lastEventTime > 500 /* 500ms taken from libinput default timeout */) {\n\n                m_scrollWheelState.accumulatedScroll = 0;\n                // send 1 discrete on first event for responsiveness\n                discrete = std::copysign(1, e.deltaDiscrete);\n            } else\n                discrete = 0;\n\n            for (int ac = m_scrollWheelState.accumulatedScroll; ac >= interval; ac -= interval) {\n                discrete += std::copysign(1, e.deltaDiscrete);\n                m_scrollWheelState.accumulatedScroll -= interval;\n            }\n\n            m_scrollWheelState.lastEventSign = std::signbit(e.deltaDiscrete);\n            m_scrollWheelState.lastEventAxis = e.axis;\n            m_scrollWheelState.lastEventTime = e.timeMs;\n            m_scrollWheelState.accumulatedScroll += std::abs(e.deltaDiscrete);\n\n            delta = 15.0 * discrete * factor;\n        }\n    }\n\n    int32_t value120      = std::round(factor * e.deltaDiscrete);\n    int32_t deltaDiscrete = std::abs(discrete) != 0 && std::abs(discrete) < 1 ? std::copysign(1, discrete) : std::round(discrete);\n\n    g_pSeatManager->sendPointerAxis(e.timeMs, e.axis, delta, deltaDiscrete, value120, e.source, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL);\n\n    const bool deferPointerFrame = e.source == WL_POINTER_AXIS_SOURCE_FINGER || e.source == WL_POINTER_AXIS_SOURCE_CONTINUOUS;\n    if (deferPointerFrame) {\n        m_pointerAxisFramePending = true;\n        return;\n    }\n\n    m_pointerAxisFramePending = false;\n    g_pSeatManager->sendPointerFrame();\n}\n\nvoid CInputManager::onPointerFrame() {\n    if (!m_pointerAxisFramePending)\n        return;\n\n    m_pointerAxisFramePending = false;\n    g_pSeatManager->sendPointerFrame();\n}\n\nVector2D CInputManager::getMouseCoordsInternal() {\n    return g_pPointerManager->position();\n}\n\nvoid CInputManager::newKeyboard(SP<IKeyboard> keeb) {\n    const auto PNEWKEYBOARD = m_keyboards.emplace_back(keeb);\n\n    setupKeyboard(PNEWKEYBOARD);\n\n    Log::logger->log(Log::DEBUG, \"New keyboard created, pointers Hypr: {:x}\", rc<uintptr_t>(PNEWKEYBOARD.get()));\n}\n\nvoid CInputManager::newKeyboard(SP<Aquamarine::IKeyboard> keyboard) {\n    const auto PNEWKEYBOARD = m_keyboards.emplace_back(CKeyboard::create(keyboard));\n\n    setupKeyboard(PNEWKEYBOARD);\n\n    Log::logger->log(Log::DEBUG, \"New keyboard created, pointers Hypr: {:x} and AQ: {:x}\", rc<uintptr_t>(PNEWKEYBOARD.get()), rc<uintptr_t>(keyboard.get()));\n}\n\nvoid CInputManager::newVirtualKeyboard(SP<CVirtualKeyboardV1Resource> keyboard) {\n    const auto PNEWKEYBOARD = m_keyboards.emplace_back(CVirtualKeyboard::create(keyboard));\n\n    setupKeyboard(PNEWKEYBOARD);\n\n    Log::logger->log(Log::DEBUG, \"New virtual keyboard created at {:x}\", rc<uintptr_t>(PNEWKEYBOARD.get()));\n}\n\nvoid CInputManager::setupKeyboard(SP<IKeyboard> keeb) {\n    static auto PDPMS = CConfigValue<Hyprlang::INT>(\"misc:key_press_enables_dpms\");\n\n    m_hids.emplace_back(keeb);\n\n    try {\n        keeb->m_hlName = getNameForNewDevice(keeb->m_deviceName);\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Keyboard had no name???\"); // logic error\n    }\n\n    keeb->m_events.destroy.listenStatic([this, keeb = keeb.get()] {\n        auto PKEEB = keeb->m_self.lock();\n\n        if (!PKEEB)\n            return;\n\n        destroyKeyboard(PKEEB);\n        Log::logger->log(Log::DEBUG, \"Destroyed keyboard {:x}\", rc<uintptr_t>(keeb));\n    });\n\n    keeb->m_keyboardEvents.key.listenStatic([this, keeb = keeb.get()](const IKeyboard::SKeyEvent& event) {\n        auto PKEEB = keeb->m_self.lock();\n\n        onKeyboardKey(event, PKEEB);\n\n        if (PKEEB->m_enabled)\n            PROTO::idle->onActivity();\n\n        if (PKEEB->m_enabled && *PDPMS && !g_pCompositor->m_dpmsStateOn)\n            g_pKeybindManager->dpms(\"on\");\n    });\n\n    keeb->m_keyboardEvents.modifiers.listenStatic([this, keeb = keeb.get()] {\n        auto PKEEB = keeb->m_self.lock();\n\n        onKeyboardMod(PKEEB);\n\n        if (PKEEB->m_enabled)\n            PROTO::idle->onActivity();\n\n        if (PKEEB->m_enabled && *PDPMS && !g_pCompositor->m_dpmsStateOn)\n            g_pKeybindManager->dpms(\"on\");\n    });\n\n    keeb->m_keyboardEvents.keymap.listenStatic([keeb = keeb.get()] {\n        auto       PKEEB  = keeb->m_self.lock();\n        const auto LAYOUT = PKEEB->getActiveLayout();\n\n        if (PKEEB == g_pSeatManager->m_keyboard) {\n            g_pSeatManager->updateActiveKeyboardData();\n            g_pKeybindManager->m_keyToCodeCache.clear();\n        }\n\n        g_pEventManager->postEvent(SHyprIPCEvent{\"activelayout\", PKEEB->m_hlName + \",\" + LAYOUT});\n        Event::bus()->m_events.input.keyboard.layout.emit(PKEEB, LAYOUT);\n    });\n\n    disableAllKeyboards(false);\n\n    applyConfigToKeyboard(keeb);\n\n    g_pSeatManager->setKeyboard(keeb);\n\n    keeb->updateLEDs();\n\n    // in case m_lastFocus was set without a keyboard\n    if (m_keyboards.size() == 1 && Desktop::focusState()->surface())\n        g_pSeatManager->setKeyboardFocus(Desktop::focusState()->surface());\n}\n\nvoid CInputManager::setKeyboardLayout() {\n    for (auto const& k : m_keyboards)\n        applyConfigToKeyboard(k);\n\n    g_pKeybindManager->updateXKBTranslationState();\n}\n\nvoid CInputManager::applyConfigToKeyboard(SP<IKeyboard> pKeyboard) {\n    auto       devname = pKeyboard->m_hlName;\n\n    const auto HASCONFIG = g_pConfigManager->deviceConfigExists(devname);\n\n    Log::logger->log(Log::DEBUG, \"ApplyConfigToKeyboard for \\\"{}\\\", hasconfig: {}\", devname, sc<int>(HASCONFIG));\n\n    const auto REPEATRATE  = g_pConfigManager->getDeviceInt(devname, \"repeat_rate\", \"input:repeat_rate\");\n    const auto REPEATDELAY = g_pConfigManager->getDeviceInt(devname, \"repeat_delay\", \"input:repeat_delay\");\n\n    const auto NUMLOCKON         = g_pConfigManager->getDeviceInt(devname, \"numlock_by_default\", \"input:numlock_by_default\");\n    const auto RESOLVEBINDSBYSYM = g_pConfigManager->getDeviceInt(devname, \"resolve_binds_by_sym\", \"input:resolve_binds_by_sym\");\n\n    const auto FILEPATH = g_pConfigManager->getDeviceString(devname, \"kb_file\", \"input:kb_file\");\n    const auto RULES    = g_pConfigManager->getDeviceString(devname, \"kb_rules\", \"input:kb_rules\");\n    const auto MODEL    = g_pConfigManager->getDeviceString(devname, \"kb_model\", \"input:kb_model\");\n    const auto LAYOUT   = g_pConfigManager->getDeviceString(devname, \"kb_layout\", \"input:kb_layout\");\n    const auto VARIANT  = g_pConfigManager->getDeviceString(devname, \"kb_variant\", \"input:kb_variant\");\n    const auto OPTIONS  = g_pConfigManager->getDeviceString(devname, \"kb_options\", \"input:kb_options\");\n\n    const auto ENABLED    = HASCONFIG ? g_pConfigManager->getDeviceInt(devname, \"enabled\") : true;\n    const auto ALLOWBINDS = HASCONFIG ? g_pConfigManager->getDeviceInt(devname, \"keybinds\") : true;\n\n    pKeyboard->m_enabled           = ENABLED;\n    pKeyboard->m_resolveBindsBySym = RESOLVEBINDSBYSYM;\n    pKeyboard->m_allowBinds        = ALLOWBINDS;\n\n    const auto PERM = g_pDynamicPermissionManager->clientPermissionModeWithString(-1, pKeyboard->m_hlName, PERMISSION_TYPE_KEYBOARD);\n\n    if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n\n        // disallow while pending\n        pKeyboard->m_allowed = false;\n\n        const auto PROMISE = g_pDynamicPermissionManager->promiseFor(-1, pKeyboard->m_hlName, PERMISSION_TYPE_KEYBOARD);\n        if (!PROMISE)\n            Log::logger->log(Log::ERR, \"BUG THIS: No promise for client permission for keyboard\");\n        else {\n            PROMISE->then([k = WP<IKeyboard>{pKeyboard}](SP<CPromiseResult<eDynamicPermissionAllowMode>> r) {\n                if (r->hasError()) {\n                    Log::logger->log(Log::ERR, \"BUG THIS: No permission returned for keyboard\");\n                    return;\n                }\n\n                if (!k)\n                    return;\n\n                k->m_allowed = r->result() == PERMISSION_RULE_ALLOW_MODE_ALLOW;\n            });\n        }\n    } else\n        pKeyboard->m_allowed = PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW;\n\n    try {\n        if (NUMLOCKON == pKeyboard->m_numlockOn && REPEATDELAY == pKeyboard->m_repeatDelay && REPEATRATE == pKeyboard->m_repeatRate && RULES == pKeyboard->m_currentRules.rules &&\n            MODEL == pKeyboard->m_currentRules.model && LAYOUT == pKeyboard->m_currentRules.layout && VARIANT == pKeyboard->m_currentRules.variant &&\n            OPTIONS == pKeyboard->m_currentRules.options && FILEPATH == pKeyboard->m_xkbFilePath) {\n            Log::logger->log(Log::DEBUG, \"Not applying config to keyboard, it did not change.\");\n            return;\n        }\n    } catch (std::exception& e) {\n        // can be libc errors for null std::string\n        // we can ignore those and just apply\n    }\n\n    pKeyboard->m_repeatRate  = std::max(0, REPEATRATE);\n    pKeyboard->m_repeatDelay = std::max(0, REPEATDELAY);\n    pKeyboard->m_numlockOn   = NUMLOCKON;\n    pKeyboard->m_xkbFilePath = FILEPATH;\n    pKeyboard->setKeymap(IKeyboard::SStringRuleNames{LAYOUT, MODEL, VARIANT, OPTIONS, RULES});\n\n    const auto LAYOUTSTR = pKeyboard->getActiveLayout();\n\n    g_pEventManager->postEvent(SHyprIPCEvent{\"activelayout\", pKeyboard->m_hlName + \",\" + LAYOUTSTR});\n    Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUTSTR);\n\n    Log::logger->log(Log::DEBUG, \"Set the keyboard layout to {} and variant to {} for keyboard \\\"{}\\\"\", pKeyboard->m_currentRules.layout, pKeyboard->m_currentRules.variant,\n                     pKeyboard->m_hlName);\n}\n\nvoid CInputManager::newVirtualMouse(SP<CVirtualPointerV1Resource> mouse) {\n    const auto PMOUSE = m_pointers.emplace_back(CVirtualPointer::create(mouse));\n\n    setupMouse(PMOUSE);\n\n    Log::logger->log(Log::DEBUG, \"New virtual mouse created\");\n}\n\nvoid CInputManager::newMouse(SP<IPointer> mouse) {\n    m_pointers.emplace_back(mouse);\n\n    setupMouse(mouse);\n\n    Log::logger->log(Log::DEBUG, \"New mouse created, pointer Hypr: {:x}\", rc<uintptr_t>(mouse.get()));\n}\n\nvoid CInputManager::newMouse(SP<Aquamarine::IPointer> mouse) {\n    const auto PMOUSE = m_pointers.emplace_back(CMouse::create(mouse));\n\n    setupMouse(PMOUSE);\n\n    Log::logger->log(Log::DEBUG, \"New mouse created, pointer AQ: {:x}\", rc<uintptr_t>(mouse.get()));\n}\n\nvoid CInputManager::setupMouse(SP<IPointer> mauz) {\n    m_hids.emplace_back(mauz);\n\n    try {\n        mauz->m_hlName = getNameForNewDevice(mauz->m_deviceName);\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Mouse had no name???\"); // logic error\n    }\n\n    if (mauz->aq() && mauz->aq()->getLibinputHandle()) {\n        const auto LIBINPUTDEV = mauz->aq()->getLibinputHandle();\n\n        Log::logger->log(Log::DEBUG, \"New mouse has libinput sens {:.2f} ({:.2f}) with accel profile {} ({})\", libinput_device_config_accel_get_speed(LIBINPUTDEV),\n                         libinput_device_config_accel_get_default_speed(LIBINPUTDEV), sc<int>(libinput_device_config_accel_get_profile(LIBINPUTDEV)),\n                         sc<int>(libinput_device_config_accel_get_default_profile(LIBINPUTDEV)));\n    }\n\n    g_pPointerManager->attachPointer(mauz);\n\n    mauz->m_connected = true;\n\n    setPointerConfigs();\n\n    mauz->m_events.destroy.listenStatic([this, PMOUSE = mauz.get()] { destroyPointer(PMOUSE->m_self.lock()); });\n\n    g_pSeatManager->setMouse(mauz);\n\n    m_lastCursorMovement.reset();\n}\n\nvoid CInputManager::setPointerConfigs() {\n    for (auto const& m : m_pointers) {\n        auto       devname = m->m_hlName;\n\n        const auto HASCONFIG = g_pConfigManager->deviceConfigExists(devname);\n\n        if (HASCONFIG) {\n            const auto ENABLED = g_pConfigManager->getDeviceInt(devname, \"enabled\");\n            if (ENABLED && !m->m_connected) {\n                g_pPointerManager->attachPointer(m);\n                m->m_connected = true;\n            } else if (!ENABLED && m->m_connected) {\n                g_pPointerManager->detachPointer(m);\n                m->m_connected = false;\n            }\n        }\n\n        if (g_pConfigManager->deviceConfigExplicitlySet(devname, \"scroll_factor\"))\n            m->m_scrollFactor = std::clamp(g_pConfigManager->getDeviceFloat(devname, \"scroll_factor\", \"input:scroll_factor\"), 0.F, 100.F);\n        else\n            m->m_scrollFactor = std::nullopt;\n\n        if (m->aq() && m->aq()->getLibinputHandle()) {\n            const auto LIBINPUTDEV = m->aq()->getLibinputHandle();\n\n            double     touchw = 0, touchh = 0;\n            const auto ISTOUCHPAD = libinput_device_has_capability(LIBINPUTDEV, LIBINPUT_DEVICE_CAP_POINTER) &&\n                libinput_device_get_size(LIBINPUTDEV, &touchw, &touchh) == 0; // pointer with size is a touchpad\n\n            if (g_pConfigManager->getDeviceInt(devname, \"clickfinger_behavior\", \"input:touchpad:clickfinger_behavior\") == 0) // toggle software buttons or clickfinger\n                libinput_device_config_click_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);\n            else\n                libinput_device_config_click_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);\n\n            if (g_pConfigManager->getDeviceInt(devname, \"left_handed\", \"input:left_handed\") == 0)\n                libinput_device_config_left_handed_set(LIBINPUTDEV, 0);\n            else\n                libinput_device_config_left_handed_set(LIBINPUTDEV, 1);\n\n            if (libinput_device_config_middle_emulation_is_available(LIBINPUTDEV)) { // middleclick on r+l mouse button pressed\n                if (g_pConfigManager->getDeviceInt(devname, \"middle_button_emulation\", \"input:touchpad:middle_button_emulation\") == 1)\n                    libinput_device_config_middle_emulation_set_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);\n                else\n                    libinput_device_config_middle_emulation_set_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);\n\n                const auto TAP_MAP = g_pConfigManager->getDeviceString(devname, \"tap_button_map\", \"input:touchpad:tap_button_map\");\n                if (TAP_MAP.empty() || TAP_MAP == \"lrm\")\n                    libinput_device_config_tap_set_button_map(LIBINPUTDEV, LIBINPUT_CONFIG_TAP_MAP_LRM);\n                else if (TAP_MAP == \"lmr\")\n                    libinput_device_config_tap_set_button_map(LIBINPUTDEV, LIBINPUT_CONFIG_TAP_MAP_LMR);\n                else\n                    Log::logger->log(Log::WARN, \"Tap button mapping unknown\");\n            }\n\n            const auto SCROLLMETHOD = g_pConfigManager->getDeviceString(devname, \"scroll_method\", \"input:scroll_method\");\n            if (SCROLLMETHOD.empty()) {\n                libinput_device_config_scroll_set_method(LIBINPUTDEV, libinput_device_config_scroll_get_default_method(LIBINPUTDEV));\n            } else if (SCROLLMETHOD == \"no_scroll\") {\n                libinput_device_config_scroll_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_SCROLL_NO_SCROLL);\n            } else if (SCROLLMETHOD == \"2fg\") {\n                libinput_device_config_scroll_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_SCROLL_2FG);\n            } else if (SCROLLMETHOD == \"edge\") {\n                libinput_device_config_scroll_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_SCROLL_EDGE);\n            } else if (SCROLLMETHOD == \"on_button_down\") {\n                libinput_device_config_scroll_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);\n            } else {\n                Log::logger->log(Log::WARN, \"Scroll method unknown\");\n            }\n\n            if (g_pConfigManager->getDeviceInt(devname, \"tap-and-drag\", \"input:touchpad:tap-and-drag\") == 0)\n                libinput_device_config_tap_set_drag_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_DRAG_DISABLED);\n            else\n                libinput_device_config_tap_set_drag_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_DRAG_ENABLED);\n\n            const auto TAP_DRAG_LOCK = g_pConfigManager->getDeviceInt(devname, \"drag_lock\", \"input:touchpad:drag_lock\");\n            if (TAP_DRAG_LOCK >= 0 && TAP_DRAG_LOCK <= 2) {\n                libinput_device_config_tap_set_drag_lock_enabled(LIBINPUTDEV, sc<libinput_config_drag_lock_state>(TAP_DRAG_LOCK));\n            }\n\n            if (libinput_device_config_tap_get_finger_count(LIBINPUTDEV)) // this is for tapping (like on a laptop)\n                libinput_device_config_tap_set_enabled(LIBINPUTDEV,\n                                                       g_pConfigManager->getDeviceInt(devname, \"tap-to-click\", \"input:touchpad:tap-to-click\") == 1 ? LIBINPUT_CONFIG_TAP_ENABLED :\n                                                                                                                                                     LIBINPUT_CONFIG_TAP_DISABLED);\n\n            if (libinput_device_config_scroll_has_natural_scroll(LIBINPUTDEV)) {\n\n                if (ISTOUCHPAD)\n                    libinput_device_config_scroll_set_natural_scroll_enabled(LIBINPUTDEV,\n                                                                             g_pConfigManager->getDeviceInt(devname, \"natural_scroll\", \"input:touchpad:natural_scroll\"));\n                else\n                    libinput_device_config_scroll_set_natural_scroll_enabled(LIBINPUTDEV, g_pConfigManager->getDeviceInt(devname, \"natural_scroll\", \"input:natural_scroll\"));\n            }\n\n            if (libinput_device_config_3fg_drag_get_finger_count(LIBINPUTDEV) >= 3) {\n                const auto DRAG_3FG_STATE = sc<libinput_config_3fg_drag_state>(g_pConfigManager->getDeviceInt(devname, \"drag_3fg\", \"input:touchpad:drag_3fg\"));\n                libinput_device_config_3fg_drag_set_enabled(LIBINPUTDEV, DRAG_3FG_STATE);\n            }\n\n            if (libinput_device_config_dwt_is_available(LIBINPUTDEV)) {\n                const auto DWT = sc<enum libinput_config_dwt_state>(g_pConfigManager->getDeviceInt(devname, \"disable_while_typing\", \"input:touchpad:disable_while_typing\") != 0);\n                libinput_device_config_dwt_set_enabled(LIBINPUTDEV, DWT);\n            }\n\n            const auto LIBINPUTSENS = std::clamp(g_pConfigManager->getDeviceFloat(devname, \"sensitivity\", \"input:sensitivity\"), -1.f, 1.f);\n            libinput_device_config_accel_set_speed(LIBINPUTDEV, LIBINPUTSENS);\n\n            if (libinput_device_config_rotation_is_available(LIBINPUTDEV)) {\n                const auto ROTATION = std::clamp(g_pConfigManager->getDeviceInt(devname, \"rotation\", \"input:rotation\"), 0, 359);\n                libinput_device_config_rotation_set_angle(LIBINPUTDEV, ROTATION);\n            }\n\n            m->m_flipX = g_pConfigManager->getDeviceInt(devname, \"flip_x\", \"input:touchpad:flip_x\") != 0;\n            m->m_flipY = g_pConfigManager->getDeviceInt(devname, \"flip_y\", \"input:touchpad:flip_y\") != 0;\n\n            const auto ACCELPROFILE = g_pConfigManager->getDeviceString(devname, \"accel_profile\", \"input:accel_profile\");\n            const auto SCROLLPOINTS = g_pConfigManager->getDeviceString(devname, \"scroll_points\", \"input:scroll_points\");\n\n            if (ACCELPROFILE.empty()) {\n                libinput_device_config_accel_set_profile(LIBINPUTDEV, libinput_device_config_accel_get_default_profile(LIBINPUTDEV));\n            } else if (ACCELPROFILE == \"adaptive\") {\n                libinput_device_config_accel_set_profile(LIBINPUTDEV, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);\n            } else if (ACCELPROFILE == \"flat\") {\n                libinput_device_config_accel_set_profile(LIBINPUTDEV, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);\n            } else if (ACCELPROFILE.starts_with(\"custom\")) {\n                CVarList accelValues = {ACCELPROFILE, 0, ' '};\n\n                try {\n                    double              accelStep = std::stod(accelValues[1]);\n                    std::vector<double> accelPoints;\n                    for (size_t i = 2; i < accelValues.size(); ++i) {\n                        accelPoints.push_back(std::stod(accelValues[i]));\n                    }\n\n                    const auto CONFIG = libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM);\n\n                    if (!SCROLLPOINTS.empty()) {\n                        CVarList scrollValues = {SCROLLPOINTS, 0, ' '};\n                        try {\n                            double              scrollStep = std::stod(scrollValues[0]);\n                            std::vector<double> scrollPoints;\n                            for (size_t i = 1; i < scrollValues.size(); ++i) {\n                                scrollPoints.push_back(std::stod(scrollValues[i]));\n                            }\n\n                            libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_SCROLL, scrollStep, scrollPoints.size(), scrollPoints.data());\n                        } catch (std::exception& e) { Log::logger->log(Log::ERR, \"Invalid values in scroll_points\"); }\n                    }\n\n                    libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_MOTION, accelStep, accelPoints.size(), accelPoints.data());\n                    libinput_device_config_accel_apply(LIBINPUTDEV, CONFIG);\n                    libinput_config_accel_destroy(CONFIG);\n                } catch (std::exception& e) { Log::logger->log(Log::ERR, \"Invalid values in custom accel profile\"); }\n            } else {\n                Log::logger->log(Log::WARN, \"Unknown acceleration profile, falling back to default\");\n            }\n\n            const auto SCROLLBUTTON = g_pConfigManager->getDeviceInt(devname, \"scroll_button\", \"input:scroll_button\");\n\n            libinput_device_config_scroll_set_button(LIBINPUTDEV, SCROLLBUTTON == 0 ? libinput_device_config_scroll_get_default_button(LIBINPUTDEV) : SCROLLBUTTON);\n\n            const auto SCROLLBUTTONLOCK = g_pConfigManager->getDeviceInt(devname, \"scroll_button_lock\", \"input:scroll_button_lock\");\n\n            libinput_device_config_scroll_set_button_lock(LIBINPUTDEV,\n                                                          SCROLLBUTTONLOCK == 0 ? LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED : LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED);\n\n            Log::logger->log(Log::DEBUG, \"Applied config to mouse {}, sens {:.2f}\", m->m_hlName, LIBINPUTSENS);\n        }\n    }\n}\n\nstatic void removeFromHIDs(WP<IHID> hid) {\n    std::erase_if(g_pInputManager->m_hids, [hid](const auto& e) { return e.expired() || e == hid; });\n    g_pInputManager->updateCapabilities();\n}\n\nvoid CInputManager::destroyKeyboard(SP<IKeyboard> pKeyboard) {\n    Log::logger->log(Log::DEBUG, \"Keyboard at {:x} removed\", rc<uintptr_t>(pKeyboard.get()));\n\n    std::erase_if(m_keyboards, [pKeyboard](const auto& other) { return other == pKeyboard; });\n\n    if (!m_keyboards.empty()) {\n        bool found = false;\n        for (auto const& k : m_keyboards | std::views::reverse) {\n            if (!k)\n                continue;\n\n            g_pSeatManager->setKeyboard(k);\n            found = true;\n            break;\n        }\n\n        if (!found)\n            g_pSeatManager->setKeyboard(nullptr);\n    } else\n        g_pSeatManager->setKeyboard(nullptr);\n\n    removeFromHIDs(pKeyboard);\n}\n\nvoid CInputManager::destroyPointer(SP<IPointer> mouse) {\n    Log::logger->log(Log::DEBUG, \"Pointer at {:x} removed\", rc<uintptr_t>(mouse.get()));\n\n    std::erase_if(m_pointers, [mouse](const auto& other) { return other == mouse; });\n\n    g_pSeatManager->setMouse(!m_pointers.empty() ? m_pointers.front() : nullptr);\n\n    if (!g_pSeatManager->m_mouse.expired())\n        unconstrainMouse();\n\n    removeFromHIDs(mouse);\n}\n\nvoid CInputManager::destroyTouchDevice(SP<ITouch> touch) {\n    Log::logger->log(Log::DEBUG, \"Touch device at {:x} removed\", rc<uintptr_t>(touch.get()));\n\n    std::erase_if(m_touches, [touch](const auto& other) { return other == touch; });\n\n    removeFromHIDs(touch);\n}\n\nvoid CInputManager::destroyTablet(SP<CTablet> tablet) {\n    Log::logger->log(Log::DEBUG, \"Tablet device at {:x} removed\", rc<uintptr_t>(tablet.get()));\n\n    std::erase_if(m_tablets, [tablet](const auto& other) { return other == tablet; });\n\n    removeFromHIDs(tablet);\n}\n\nvoid CInputManager::destroyTabletTool(SP<CTabletTool> tool) {\n    Log::logger->log(Log::DEBUG, \"Tablet tool at {:x} removed\", rc<uintptr_t>(tool.get()));\n\n    std::erase_if(m_tabletTools, [tool](const auto& other) { return other == tool; });\n\n    removeFromHIDs(tool);\n}\n\nvoid CInputManager::destroyTabletPad(SP<CTabletPad> pad) {\n    Log::logger->log(Log::DEBUG, \"Tablet pad at {:x} removed\", rc<uintptr_t>(pad.get()));\n\n    std::erase_if(m_tabletPads, [pad](const auto& other) { return other == pad; });\n\n    removeFromHIDs(pad);\n}\n\nvoid CInputManager::updateKeyboardsLeds(SP<IKeyboard> pKeyboard) {\n    if (!pKeyboard || pKeyboard->isVirtual())\n        return;\n\n    std::optional<uint32_t> leds = pKeyboard->getLEDs();\n\n    if (!leds.has_value())\n        return;\n\n    for (auto const& k : m_keyboards) {\n        k->updateLEDs(leds.value());\n    }\n}\n\nvoid CInputManager::onKeyboardKey(const IKeyboard::SKeyEvent& event, SP<IKeyboard> pKeyboard) {\n    if (!pKeyboard->m_enabled || !pKeyboard->m_allowed)\n        return;\n\n    const bool           DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard);\n\n    const auto           IME    = m_relay.m_inputMethod.lock();\n    const bool           HASIME = IME && IME->hasGrab();\n    const bool           USEIME = HASIME && !DISALLOWACTION;\n\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.keyboard.key.emit(event, info);\n    if (info.cancelled)\n        return;\n\n    bool passEvent = DISALLOWACTION;\n\n    if (!DISALLOWACTION)\n        passEvent = g_pKeybindManager->onKeyEvent(event, pKeyboard);\n\n    if (passEvent) {\n        auto state   = event.state;\n        auto pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED;\n\n        // use merged keys states when sending to ime or when sending to seat with no ime\n        // if passing from ime, send keys directly without merging\n        if (USEIME || !HASIME) {\n            const auto ANYPRESSED = shareKeyFromAllKBs(event.keycode, pressed);\n\n            // do not turn released event into pressed event (when one keyboard has a key released but some\n            // other keyboard still has the key pressed)\n            // maybe we should keep track of pressed keys for inputs like m_pressed for seat outputs below,\n            // to avoid duplicate pressed events, but this should work well enough\n            if (!pressed && ANYPRESSED)\n                return;\n\n            pressed = ANYPRESSED;\n            state   = pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED;\n        }\n\n        if (USEIME) {\n            IME->setKeyboard(pKeyboard);\n            IME->sendKey(event.timeMs, event.keycode, state);\n        } else {\n            const auto CONTAINS = std::ranges::contains(m_pressed, event.keycode);\n\n            if (CONTAINS && pressed)\n                return;\n            if (!CONTAINS && !pressed)\n                return;\n\n            if (CONTAINS)\n                std::erase(m_pressed, event.keycode);\n            else\n                m_pressed.emplace_back(event.keycode);\n\n            g_pSeatManager->setKeyboard(pKeyboard);\n            g_pSeatManager->sendKeyboardKey(event.timeMs, event.keycode, state);\n        }\n\n        updateKeyboardsLeds(pKeyboard);\n    }\n}\n\nvoid CInputManager::onKeyboardMod(SP<IKeyboard> pKeyboard) {\n    if (!pKeyboard->m_enabled)\n        return;\n\n    const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard);\n\n    const auto IME    = m_relay.m_inputMethod.lock();\n    const bool HASIME = IME && IME->hasGrab();\n    const bool USEIME = HASIME && !DISALLOWACTION;\n\n    auto       MODS = pKeyboard->m_modifiersState;\n\n    // use merged mods states when sending to ime or when sending to seat with no ime\n    // if passing from ime, send mods directly without merging\n    if (USEIME || !HASIME) {\n        const auto ALLMODS = shareModsFromAllKBs(MODS.depressed);\n        MODS.depressed     = ALLMODS;\n        m_lastMods         = MODS.depressed; // for hyprland keybinds use; not for sending to seat\n    }\n\n    if (USEIME) {\n        IME->setKeyboard(pKeyboard);\n        IME->sendMods(MODS.depressed, MODS.latched, MODS.locked, MODS.group);\n    } else {\n        g_pSeatManager->setKeyboard(pKeyboard);\n        g_pSeatManager->sendKeyboardMods(MODS.depressed, MODS.latched, MODS.locked, MODS.group);\n    }\n\n    updateKeyboardsLeds(pKeyboard);\n\n    if (pKeyboard->m_modifiersState.group != pKeyboard->m_activeLayout) {\n        pKeyboard->m_activeLayout = pKeyboard->m_modifiersState.group;\n\n        const auto LAYOUT = pKeyboard->getActiveLayout();\n\n        Log::logger->log(Log::DEBUG, \"LAYOUT CHANGED TO {} GROUP {}\", LAYOUT, MODS.group);\n\n        g_pEventManager->postEvent(SHyprIPCEvent{\"activelayout\", pKeyboard->m_hlName + \",\" + LAYOUT});\n        Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUT);\n    }\n}\n\nbool CInputManager::shouldIgnoreVirtualKeyboard(SP<IKeyboard> pKeyboard) {\n    if (!pKeyboard)\n        return true;\n\n    if (!pKeyboard->isVirtual())\n        return false;\n\n    const auto CLIENT = pKeyboard->getClient();\n\n    const auto DISALLOWACTION = CLIENT && !m_relay.m_inputMethod.expired() && m_relay.m_inputMethod->grabClient() == CLIENT;\n\n    if (DISALLOWACTION)\n        pKeyboard->setShareStatesAuto(false);\n\n    return DISALLOWACTION;\n}\n\nvoid CInputManager::refocus(std::optional<Vector2D> overridePos) {\n    mouseMoveUnified(0, true, false, overridePos);\n}\n\nbool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) {\n    if (!m_exclusiveLSes.empty()) {\n        Log::logger->log(Log::DEBUG, \"CInputManager::refocusLastWindow: ignoring, exclusive LS present.\");\n        return false;\n    }\n\n    if (!pMonitor) {\n        refocus();\n        return true;\n    }\n\n    Vector2D               surfaceCoords;\n    PHLLS                  pFoundLayerSurface;\n    SP<CWLSurfaceResource> foundSurface = nullptr;\n\n    g_pInputManager->releaseAllMouseButtons();\n\n    // then any surfaces above windows on the same monitor\n    if (!foundSurface) {\n        foundSurface = g_pCompositor->vectorToLayerSurface(g_pInputManager->getMouseCoordsInternal(), &pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],\n                                                           &surfaceCoords, &pFoundLayerSurface);\n        if (pFoundLayerSurface && pFoundLayerSurface->m_interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND)\n            foundSurface = nullptr;\n    }\n\n    if (!foundSurface) {\n        foundSurface = g_pCompositor->vectorToLayerSurface(g_pInputManager->getMouseCoordsInternal(), &pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],\n                                                           &surfaceCoords, &pFoundLayerSurface);\n        if (pFoundLayerSurface && pFoundLayerSurface->m_interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND)\n            foundSurface = nullptr;\n    }\n\n    if (!foundSurface && Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace && Desktop::focusState()->window()->m_workspace->isVisibleNotCovered()) {\n        // then the last focused window if we're on the same workspace as it\n        const auto PLASTWINDOW = Desktop::focusState()->window();\n        Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM);\n    } else {\n        // otherwise fall back to a normal refocus.\n\n        if (foundSurface && !foundSurface->m_hlSurface->keyboardFocusable()) {\n            const auto PLASTWINDOW = Desktop::focusState()->window();\n            Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM);\n        }\n\n        refocus();\n    }\n\n    return true;\n}\n\nvoid CInputManager::unconstrainMouse() {\n    if (g_pSeatManager->m_mouse.expired())\n        return;\n\n    for (auto const& c : m_constraints) {\n        const auto C = c.lock();\n\n        if (!C)\n            continue;\n\n        if (!C->isActive())\n            continue;\n\n        C->deactivate();\n    }\n}\n\nbool CInputManager::isConstrained() {\n    return std::ranges::any_of(m_constraints, [](auto const& c) {\n        const auto constraint = c.lock();\n        return constraint && constraint->isActive() && constraint->owner()->resource() == Desktop::focusState()->surface();\n    });\n}\n\nbool CInputManager::isLocked() {\n    if (!isConstrained())\n        return false;\n\n    const auto SURF       = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface());\n    const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr;\n\n    return CONSTRAINT && CONSTRAINT->isLocked();\n}\n\nvoid CInputManager::updateCapabilities() {\n    uint32_t caps = 0;\n\n    for (auto const& h : m_hids) {\n        if (h.expired())\n            continue;\n\n        caps |= h->getCapabilities();\n    }\n\n    g_pSeatManager->updateCapabilities(caps);\n    m_capabilities = caps;\n}\n\nconst std::vector<uint32_t>& CInputManager::getKeysFromAllKBs() {\n    return m_pressed;\n}\n\nuint32_t CInputManager::getModsFromAllKBs() {\n    return m_lastMods;\n}\n\nbool CInputManager::shareKeyFromAllKBs(uint32_t key, bool pressed) {\n    bool finalState = pressed;\n\n    if (finalState)\n        return finalState;\n\n    for (auto const& kb : m_keyboards) {\n        if (!kb->shareStates())\n            continue;\n\n        if (kb->isVirtual() && shouldIgnoreVirtualKeyboard(kb))\n            continue;\n\n        if (!kb->m_enabled)\n            continue;\n\n        const bool PRESSED = kb->getPressed(key);\n        if (PRESSED)\n            return PRESSED;\n    }\n\n    return finalState;\n}\n\nuint32_t CInputManager::shareModsFromAllKBs(uint32_t depressed) {\n    uint32_t finalMask = depressed;\n\n    for (auto const& kb : m_keyboards) {\n        if (!kb->shareStates())\n            continue;\n\n        if (kb->isVirtual() && shouldIgnoreVirtualKeyboard(kb))\n            continue;\n\n        if (!kb->m_enabled)\n            continue;\n\n        finalMask |= kb->getModifiers();\n    }\n\n    return finalMask;\n}\n\nvoid CInputManager::disableAllKeyboards(bool virt) {\n\n    for (auto const& k : m_keyboards) {\n        if (k->isVirtual() != virt)\n            continue;\n\n        k->m_active = false;\n    }\n}\n\nvoid CInputManager::newTouchDevice(SP<Aquamarine::ITouch> pDevice) {\n    const auto PNEWDEV = m_touches.emplace_back(CTouchDevice::create(pDevice));\n    m_hids.emplace_back(PNEWDEV);\n\n    try {\n        PNEWDEV->m_hlName = getNameForNewDevice(PNEWDEV->m_deviceName);\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Touch Device had no name???\"); // logic error\n    }\n\n    setTouchDeviceConfigs(PNEWDEV);\n    g_pPointerManager->attachTouch(PNEWDEV);\n\n    PNEWDEV->m_events.destroy.listenStatic([this, dev = PNEWDEV.get()] {\n        auto PDEV = dev->m_self.lock();\n\n        if (!PDEV)\n            return;\n\n        destroyTouchDevice(PDEV);\n    });\n\n    Log::logger->log(Log::DEBUG, \"New touch device added at {:x}\", rc<uintptr_t>(PNEWDEV.get()));\n}\n\nvoid CInputManager::setTouchDeviceConfigs(SP<ITouch> dev) {\n    auto setConfig = [](SP<ITouch> PTOUCHDEV) -> void {\n        if (PTOUCHDEV->aq() && PTOUCHDEV->aq()->getLibinputHandle()) {\n            const auto LIBINPUTDEV = PTOUCHDEV->aq()->getLibinputHandle();\n\n            const auto ENABLED = g_pConfigManager->getDeviceInt(PTOUCHDEV->m_hlName, \"enabled\", \"input:touchdevice:enabled\");\n            const auto mode    = ENABLED ? LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;\n            if (libinput_device_config_send_events_get_mode(LIBINPUTDEV) != mode)\n                libinput_device_config_send_events_set_mode(LIBINPUTDEV, mode);\n\n            if (libinput_device_config_calibration_has_matrix(LIBINPUTDEV)) {\n                Log::logger->log(Log::DEBUG, \"Setting calibration matrix for device {}\", PTOUCHDEV->m_hlName);\n                // default value of transform being -1 means it's unset.\n                const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(PTOUCHDEV->m_hlName, \"transform\", \"input:touchdevice:transform\"), -1, 7);\n                if (ROTATION > -1)\n                    libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]);\n            }\n\n            auto       output     = g_pConfigManager->getDeviceString(PTOUCHDEV->m_hlName, \"output\", \"input:touchdevice:output\");\n            bool       bound      = !output.empty() && output != STRVAL_EMPTY;\n            const bool AUTODETECT = output == \"[[Auto]]\";\n            if (!bound && AUTODETECT) {\n                // FIXME:\n                // const auto DEFAULTOUTPUT = PTOUCHDEV->wlr()->output_name;\n                // if (DEFAULTOUTPUT) {\n                //     output = DEFAULTOUTPUT;\n                //     bound  = true;\n                // }\n            }\n            PTOUCHDEV->m_boundOutput = bound ? output : \"\";\n            const auto PMONITOR      = bound ? g_pCompositor->getMonitorFromName(output) : nullptr;\n            if (PMONITOR) {\n                Log::logger->log(Log::DEBUG, \"Binding touch device {} to output {}\", PTOUCHDEV->m_hlName, PMONITOR->m_name);\n                // wlr_cursor_map_input_to_output(g_pCompositor->m_sWLRCursor, &PTOUCHDEV->wlr()->base, PMONITOR->output);\n            } else if (bound)\n                Log::logger->log(Log::ERR, \"Failed to bind touch device {} to output '{}': monitor not found\", PTOUCHDEV->m_hlName, output);\n        }\n    };\n\n    if (dev) {\n        setConfig(dev);\n        return;\n    }\n\n    for (auto const& m : m_touches) {\n        setConfig(m);\n    }\n}\n\nvoid CInputManager::setTabletConfigs() {\n    for (auto const& t : m_tablets) {\n        if (t->aq()->getLibinputHandle()) {\n            const auto NAME        = t->m_hlName;\n            const auto LIBINPUTDEV = t->aq()->getLibinputHandle();\n\n            const auto RELINPUT = g_pConfigManager->getDeviceInt(NAME, \"relative_input\", \"input:tablet:relative_input\");\n            t->m_relativeInput  = RELINPUT;\n\n            const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(NAME, \"transform\", \"input:tablet:transform\"), -1, 7);\n            Log::logger->log(Log::DEBUG, \"Setting calibration matrix for device {}\", NAME);\n            if (ROTATION > -1)\n                libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]);\n\n            if (g_pConfigManager->getDeviceInt(NAME, \"left_handed\", \"input:tablet:left_handed\") == 0)\n                libinput_device_config_left_handed_set(LIBINPUTDEV, 0);\n            else\n                libinput_device_config_left_handed_set(LIBINPUTDEV, 1);\n\n            const auto OUTPUT = g_pConfigManager->getDeviceString(NAME, \"output\", \"input:tablet:output\");\n            if (OUTPUT != STRVAL_EMPTY) {\n                Log::logger->log(Log::DEBUG, \"Binding tablet {} to output {}\", NAME, OUTPUT);\n                t->m_boundOutput = OUTPUT;\n            } else\n                t->m_boundOutput = \"\";\n\n            const auto REGION_POS  = g_pConfigManager->getDeviceVec(NAME, \"region_position\", \"input:tablet:region_position\");\n            const auto REGION_SIZE = g_pConfigManager->getDeviceVec(NAME, \"region_size\", \"input:tablet:region_size\");\n            t->m_boundBox          = {REGION_POS, REGION_SIZE};\n\n            const auto ABSOLUTE_REGION_POS = g_pConfigManager->getDeviceInt(NAME, \"absolute_region_position\", \"input:tablet:absolute_region_position\");\n            t->m_absolutePos               = ABSOLUTE_REGION_POS;\n\n            const auto ACTIVE_AREA_SIZE = g_pConfigManager->getDeviceVec(NAME, \"active_area_size\", \"input:tablet:active_area_size\");\n            const auto ACTIVE_AREA_POS  = g_pConfigManager->getDeviceVec(NAME, \"active_area_position\", \"input:tablet:active_area_position\");\n            if (ACTIVE_AREA_SIZE.x != 0 || ACTIVE_AREA_SIZE.y != 0) {\n                // Rotations with an odd index (90 and 270 degrees, and their flipped variants) swap the X and Y axes.\n                // Use swapped dimensions when the axes are rotated, otherwise keep the original ones.\n                const Vector2D effectivePhysicalSize = (ROTATION % 2) ? Vector2D{t->aq()->physicalSize.y, t->aq()->physicalSize.x} : t->aq()->physicalSize;\n\n                // Scale the active area coordinates into normalized space (0–1) using the effective dimensions.\n                t->m_activeArea = CBox{ACTIVE_AREA_POS.x / effectivePhysicalSize.x, ACTIVE_AREA_POS.y / effectivePhysicalSize.y,\n                                       (ACTIVE_AREA_POS.x + ACTIVE_AREA_SIZE.x) / effectivePhysicalSize.x, (ACTIVE_AREA_POS.y + ACTIVE_AREA_SIZE.y) / effectivePhysicalSize.y};\n            }\n        }\n    }\n}\n\nvoid CInputManager::newSwitch(SP<Aquamarine::ISwitch> pDevice) {\n    const auto PNEWDEV = &m_switches.emplace_back();\n    PNEWDEV->pDevice   = pDevice;\n\n    Log::logger->log(Log::DEBUG, \"New switch with name \\\"{}\\\" added\", pDevice->getName());\n\n    PNEWDEV->listeners.destroy = pDevice->events.destroy.listen([this, PNEWDEV] { destroySwitch(PNEWDEV); });\n\n    PNEWDEV->listeners.fire = pDevice->events.fire.listen([PNEWDEV](const Aquamarine::ISwitch::SFireEvent& event) {\n        const auto NAME = PNEWDEV->pDevice->getName();\n\n        Log::logger->log(Log::DEBUG, \"Switch {} fired, triggering binds.\", NAME);\n\n        g_pKeybindManager->onSwitchEvent(NAME);\n\n        if (event.enable) {\n            Log::logger->log(Log::DEBUG, \"Switch {} turn on, triggering binds.\", NAME);\n            g_pKeybindManager->onSwitchOnEvent(NAME);\n        } else {\n            Log::logger->log(Log::DEBUG, \"Switch {} turn off, triggering binds.\", NAME);\n            g_pKeybindManager->onSwitchOffEvent(NAME);\n        }\n    });\n}\n\nvoid CInputManager::destroySwitch(SSwitchDevice* pDevice) {\n    m_switches.remove(*pDevice);\n}\n\nstd::string CInputManager::getNameForNewDevice(std::string internalName) {\n\n    auto proposedNewName = deviceNameToInternalString(internalName);\n    int  dupeno          = 0;\n\n    auto makeNewName = [&]() { return (proposedNewName.empty() ? \"unknown-device\" : proposedNewName) + (dupeno == 0 ? \"\" : (\"-\" + std::to_string(dupeno))); };\n\n    while (std::ranges::find_if(m_hids, [&](const auto& other) { return other->m_hlName == makeNewName(); }) != m_hids.end())\n        dupeno++;\n\n    return makeNewName();\n}\n\nvoid CInputManager::releaseAllMouseButtons() {\n    const auto buttonsCopy = m_currentlyHeldButtons;\n\n    if (PROTO::data->dndActive())\n        return;\n\n    for (auto const& mb : buttonsCopy) {\n        g_pSeatManager->sendPointerButton(Time::millis(Time::steadyNow()), mb, WL_POINTER_BUTTON_STATE_RELEASED);\n    }\n\n    m_currentlyHeldButtons.clear();\n}\n\nvoid CInputManager::setCursorIconOnBorder(PHLWINDOW w) {\n    // ignore X11 OR windows, they shouldn't be touched\n    if (w->m_isX11 && w->isX11OverrideRedirect()) {\n        Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE);\n        return;\n    }\n\n    static auto PEXTENDBORDERGRAB = CConfigValue<Hyprlang::INT>(\"general:extend_border_grab_area\");\n    const int   BORDERSIZE        = w->getRealBorderSize();\n    const int   ROUNDING          = w->rounding();\n\n    // give a small leeway (10 px) for corner icon\n    const auto           CORNER           = ROUNDING + BORDERSIZE + 10;\n    const auto           mouseCoords      = getMouseCoordsInternal();\n    CBox                 box              = w->getWindowMainSurfaceBox();\n    eBorderIconDirection direction        = BORDERICON_NONE;\n    CBox                 boxFullGrabInput = {box.x - *PEXTENDBORDERGRAB - BORDERSIZE, box.y - *PEXTENDBORDERGRAB - BORDERSIZE, box.width + 2 * (*PEXTENDBORDERGRAB + BORDERSIZE),\n                                             box.height + 2 * (*PEXTENDBORDERGRAB + BORDERSIZE)};\n\n    if (w->hasPopupAt(mouseCoords))\n        direction = BORDERICON_NONE;\n    else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && !g_layoutManager->dragController()->target()))\n        direction = BORDERICON_NONE;\n    else {\n\n        bool onDeco = false;\n\n        for (auto const& wd : w->m_windowDecorations) {\n            if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT))\n                continue;\n\n            if (g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords)) {\n                onDeco = true;\n                break;\n            }\n        }\n\n        if (onDeco)\n            direction = BORDERICON_NONE;\n        else {\n            if (box.containsPoint(mouseCoords)) {\n                if (!w->isInCurvedCorner(mouseCoords.x, mouseCoords.y)) {\n                    direction = BORDERICON_NONE;\n                } else {\n                    if (mouseCoords.y < box.y + CORNER) {\n                        if (mouseCoords.x < box.x + CORNER)\n                            direction = BORDERICON_UP_LEFT;\n                        else\n                            direction = BORDERICON_UP_RIGHT;\n                    } else {\n                        if (mouseCoords.x < box.x + CORNER)\n                            direction = BORDERICON_DOWN_LEFT;\n                        else\n                            direction = BORDERICON_DOWN_RIGHT;\n                    }\n                }\n            } else {\n                if (mouseCoords.y < box.y + CORNER) {\n                    if (mouseCoords.x < box.x + CORNER)\n                        direction = BORDERICON_UP_LEFT;\n                    else if (mouseCoords.x > box.x + box.width - CORNER)\n                        direction = BORDERICON_UP_RIGHT;\n                    else\n                        direction = BORDERICON_UP;\n                } else if (mouseCoords.y > box.y + box.height - CORNER) {\n                    if (mouseCoords.x < box.x + CORNER)\n                        direction = BORDERICON_DOWN_LEFT;\n                    else if (mouseCoords.x > box.x + box.width - CORNER)\n                        direction = BORDERICON_DOWN_RIGHT;\n                    else\n                        direction = BORDERICON_DOWN;\n                } else {\n                    if (mouseCoords.x < box.x + CORNER)\n                        direction = BORDERICON_LEFT;\n                    else if (mouseCoords.x > box.x + box.width - CORNER)\n                        direction = BORDERICON_RIGHT;\n                }\n            }\n        }\n    }\n\n    if (direction == m_borderIconDirection)\n        return;\n\n    m_borderIconDirection = direction;\n\n    switch (direction) {\n        case BORDERICON_NONE: Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_UP: Cursor::overrideController->setOverride(\"top_side\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_DOWN: Cursor::overrideController->setOverride(\"bottom_side\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_LEFT: Cursor::overrideController->setOverride(\"left_side\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_RIGHT: Cursor::overrideController->setOverride(\"right_side\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_UP_LEFT: Cursor::overrideController->setOverride(\"top_left_corner\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_DOWN_LEFT: Cursor::overrideController->setOverride(\"bottom_left_corner\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_UP_RIGHT: Cursor::overrideController->setOverride(\"top_right_corner\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n        case BORDERICON_DOWN_RIGHT: Cursor::overrideController->setOverride(\"bottom_right_corner\", Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); break;\n    }\n}\n\nvoid CInputManager::recheckMouseWarpOnMouseInput() {\n    static auto PWARPFORNONMOUSE = CConfigValue<Hyprlang::INT>(\"cursor:warp_back_after_non_mouse_input\");\n\n    if (!m_lastInputMouse && *PWARPFORNONMOUSE)\n        g_pPointerManager->warpTo(m_lastMousePos);\n}\n\nvoid CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.gesture.swipe.begin.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    g_pTrackpadGestures->gestureBegin(e);\n\n    PROTO::pointerGestures->swipeBegin(e.timeMs, e.fingers);\n}\n\nvoid CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.gesture.swipe.update.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    g_pTrackpadGestures->gestureUpdate(e);\n\n    PROTO::pointerGestures->swipeUpdate(e.timeMs, e.delta);\n}\n\nvoid CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.gesture.swipe.end.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    g_pTrackpadGestures->gestureEnd(e);\n\n    PROTO::pointerGestures->swipeEnd(e.timeMs, e.cancelled);\n}\n\nvoid CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.gesture.pinch.begin.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    g_pTrackpadGestures->gestureBegin(e);\n\n    PROTO::pointerGestures->pinchBegin(e.timeMs, e.fingers);\n}\n\nvoid CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.gesture.pinch.update.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    g_pTrackpadGestures->gestureUpdate(e);\n\n    PROTO::pointerGestures->pinchUpdate(e.timeMs, e.delta, e.scale, e.rotation);\n}\n\nvoid CInputManager::onPinchEnd(IPointer::SPinchEndEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.gesture.pinch.end.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    g_pTrackpadGestures->gestureEnd(e);\n\n    PROTO::pointerGestures->pinchEnd(e.timeMs, e.cancelled);\n}\n"
  },
  {
    "path": "src/managers/input/InputManager.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include <list>\n#include <any>\n#include \"../../helpers/WLClasses.hpp\"\n#include \"../../helpers/time/Timer.hpp\"\n#include \"InputMethodRelay.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../../desktop/view/WLSurface.hpp\"\n#include \"../../devices/IPointer.hpp\"\n#include \"../../devices/ITouch.hpp\"\n#include \"../../devices/IKeyboard.hpp\"\n#include \"../../devices/Tablet.hpp\"\n#include \"../SessionLockManager.hpp\"\n#include \"../SeatManager.hpp\"\n\nclass CPointerConstraint;\nclass CIdleInhibitor;\nclass CVirtualKeyboardV1Resource;\nclass CVirtualPointerV1Resource;\nclass IKeyboard;\n\nAQUAMARINE_FORWARD(IPointer);\nAQUAMARINE_FORWARD(IKeyboard);\nAQUAMARINE_FORWARD(ITouch);\nAQUAMARINE_FORWARD(ISwitch);\nAQUAMARINE_FORWARD(ITablet);\nAQUAMARINE_FORWARD(ITabletTool);\nAQUAMARINE_FORWARD(ITabletPad);\n\nenum eClickBehaviorMode : uint8_t {\n    CLICKMODE_DEFAULT = 0,\n    CLICKMODE_KILL\n};\n\nenum eMouseBindMode : int8_t {\n    MBIND_INVALID            = -1,\n    MBIND_MOVE               = 0,\n    MBIND_RESIZE             = 1,\n    MBIND_RESIZE_BLOCK_RATIO = 2,\n    MBIND_RESIZE_FORCE_RATIO = 3\n};\n\nenum eBorderIconDirection : uint8_t {\n    BORDERICON_NONE = 0,\n    BORDERICON_UP,\n    BORDERICON_DOWN,\n    BORDERICON_LEFT,\n    BORDERICON_RIGHT,\n    BORDERICON_UP_LEFT,\n    BORDERICON_DOWN_LEFT,\n    BORDERICON_UP_RIGHT,\n    BORDERICON_DOWN_RIGHT,\n};\n\nstruct STouchData {\n    WP<SSessionLockSurface> touchFocusLockSurface;\n    PHLWINDOWREF            touchFocusWindow;\n    PHLLSREF                touchFocusLS;\n    WP<CWLSurfaceResource>  touchFocusSurface;\n    Vector2D                touchSurfaceOrigin;\n};\n\n// The third row is always 0 0 1 and is not expected by `libinput_device_config_calibration_set_matrix`\nstatic const float MATRICES[8][6] = {{// normal\n                                      1, 0, 0, 0, 1, 0},\n                                     {// rotation 90°\n                                      0, -1, 1, 1, 0, 0},\n                                     {// rotation 180°\n                                      -1, 0, 1, 0, -1, 1},\n                                     {// rotation 270°\n                                      0, 1, 0, -1, 0, 1},\n                                     {// flipped\n                                      -1, 0, 1, 0, 1, 0},\n                                     {// flipped + rotation 90°\n                                      0, 1, 0, 1, 0, 0},\n                                     {// flipped + rotation 180°\n                                      1, 0, 0, 0, -1, 1},\n                                     {// flipped + rotation 270°\n                                      0, -1, 1, -1, 0, 1}};\n\nclass CKeybindManager;\n\nclass CInputManager {\n  public:\n    CInputManager();\n    ~CInputManager();\n\n    void               onMouseMoved(IPointer::SMotionEvent);\n    void               onMouseWarp(IPointer::SMotionAbsoluteEvent);\n    void               onMouseButton(IPointer::SButtonEvent, SP<IPointer>);\n    void               onMouseWheel(IPointer::SAxisEvent, SP<IPointer> pointer = nullptr);\n    void               onPointerFrame();\n    void               onKeyboardKey(const IKeyboard::SKeyEvent&, SP<IKeyboard>);\n    void               onKeyboardMod(SP<IKeyboard>);\n\n    void               newKeyboard(SP<IKeyboard>);\n    void               newKeyboard(SP<Aquamarine::IKeyboard>);\n    void               newVirtualKeyboard(SP<CVirtualKeyboardV1Resource>);\n    void               newMouse(SP<IPointer>);\n    void               newMouse(SP<Aquamarine::IPointer>);\n    void               newVirtualMouse(SP<CVirtualPointerV1Resource>);\n    void               newTouchDevice(SP<Aquamarine::ITouch>);\n    void               newSwitch(SP<Aquamarine::ISwitch>);\n    void               newTabletPad(SP<Aquamarine::ITabletPad>);\n    void               newTablet(SP<Aquamarine::ITablet>);\n    void               destroyTouchDevice(SP<ITouch>);\n    void               destroyKeyboard(SP<IKeyboard>);\n    void               destroyPointer(SP<IPointer>);\n    void               destroyTablet(SP<CTablet>);\n    void               destroyTabletTool(SP<CTabletTool>);\n    void               destroyTabletPad(SP<CTabletPad>);\n    void               destroySwitch(SSwitchDevice*);\n\n    void               unconstrainMouse();\n    bool               isConstrained();\n    bool               isLocked();\n\n    Vector2D           getMouseCoordsInternal();\n    void               refocus(std::optional<Vector2D> overridePos = std::nullopt);\n    bool               refocusLastWindow(PHLMONITOR pMonitor);\n    void               simulateMouseMovement();\n    void               sendMotionEventsToFocused();\n\n    void               setKeyboardLayout();\n    void               setPointerConfigs();\n    void               setTouchDeviceConfigs(SP<ITouch> dev = nullptr);\n    void               setTabletConfigs();\n\n    void               updateCapabilities();\n    void               updateKeyboardsLeds(SP<IKeyboard>);\n\n    void               setClickMode(eClickBehaviorMode);\n    eClickBehaviorMode getClickMode();\n    void               processMouseRequest(const CSeatManager::SSetCursorEvent& event);\n\n    void               onTouchDown(ITouch::SDownEvent);\n    void               onTouchUp(ITouch::SUpEvent);\n    void               onTouchMove(ITouch::SMotionEvent);\n\n    void               onSwipeBegin(IPointer::SSwipeBeginEvent);\n    void               onSwipeEnd(IPointer::SSwipeEndEvent);\n    void               onSwipeUpdate(IPointer::SSwipeUpdateEvent);\n\n    void               onPinchBegin(IPointer::SPinchBeginEvent);\n    void               onPinchUpdate(IPointer::SPinchUpdateEvent);\n    void               onPinchEnd(IPointer::SPinchEndEvent);\n\n    void               onTabletAxis(CTablet::SAxisEvent);\n    void               onTabletProximity(CTablet::SProximityEvent);\n    void               onTabletTip(CTablet::STipEvent);\n    void               onTabletButton(CTablet::SButtonEvent);\n\n    STouchData         m_touchData;\n\n    // for refocus to be forced\n    PHLWINDOWREF                 m_forcedFocus;\n\n    std::vector<SP<IKeyboard>>   m_keyboards;\n    std::vector<SP<IPointer>>    m_pointers;\n    std::vector<SP<ITouch>>      m_touches;\n    std::vector<SP<CTablet>>     m_tablets;\n    std::vector<SP<CTabletTool>> m_tabletTools;\n    std::vector<SP<CTabletPad>>  m_tabletPads;\n    std::vector<WP<IHID>>        m_hids; // general container for all HID devices connected to the input manager.\n\n    // Switches\n    std::list<SSwitchDevice> m_switches;\n\n    // Exclusive layer surfaces\n    std::vector<PHLLSREF> m_exclusiveLSes;\n\n    // constraints\n    std::vector<WP<CPointerConstraint>> m_constraints;\n\n    //\n    void              newIdleInhibitor(std::any);\n    void              recheckIdleInhibitorStatus();\n    bool              isWindowInhibiting(const PHLWINDOW& pWindow, bool onlyHl = true);\n\n    CTimer            m_lastCursorMovement;\n\n    CInputMethodRelay m_relay;\n\n    // for shared mods\n    const std::vector<uint32_t>& getKeysFromAllKBs();\n    uint32_t                     getModsFromAllKBs();\n\n    // for virtual keyboards: whether we should respect them as normal ones\n    bool        shouldIgnoreVirtualKeyboard(SP<IKeyboard>);\n\n    std::string getNameForNewDevice(std::string);\n\n    void        releaseAllMouseButtons();\n\n    // for some bugs in follow mouse 0\n    bool m_lastFocusOnLS = false;\n\n    // for hard input e.g. clicks\n    bool m_hardInput = false;\n\n    // for hiding cursor on touch\n    bool m_lastInputTouch = false;\n\n    // for hiding cursor on tablet\n    bool m_lastInputTablet = false;\n\n    // for tracking mouse refocus\n    PHLWINDOWREF m_lastMouseFocus;\n\n    //\n    bool m_emptyFocusCursorSet = false;\n\n  private:\n    // Listeners\n    struct {\n        CHyprSignalListener setCursorShape;\n        CHyprSignalListener newIdleInhibitor;\n        CHyprSignalListener newVirtualKeyboard;\n        CHyprSignalListener newVirtualMouse;\n        CHyprSignalListener setCursor;\n        CHyprSignalListener overrideChanged;\n    } m_listeners;\n\n    bool                 m_cursorImageOverridden = false;\n    eBorderIconDirection m_borderIconDirection   = BORDERICON_NONE;\n\n    // for click behavior override\n    eClickBehaviorMode m_clickBehavior        = CLICKMODE_DEFAULT;\n    Vector2D           m_lastCursorPosFloored = Vector2D();\n\n    void               setupKeyboard(SP<IKeyboard> keeb);\n    void               setupMouse(SP<IPointer> mauz);\n\n    void               processMouseDownNormal(const IPointer::SButtonEvent& e, SP<IPointer>);\n    void               processMouseDownKill(const IPointer::SButtonEvent& e);\n\n    bool               cursorImageUnlocked();\n\n    void               disableAllKeyboards(bool virt = false);\n\n    uint32_t           m_capabilities = 0;\n\n    void               mouseMoveUnified(uint32_t, bool refocus = false, bool mouse = false, std::optional<Vector2D> overridePos = std::nullopt);\n    void               recheckMouseWarpOnMouseInput();\n\n    SP<CTabletTool>    ensureTabletToolPresent(SP<Aquamarine::ITabletTool>);\n\n    void               applyConfigToKeyboard(SP<IKeyboard>);\n\n    // this will be set after a refocus()\n    WP<CWLSurfaceResource> m_foundSurfaceToFocus;\n    PHLLSREF               m_foundLSToFocus;\n    PHLWINDOWREF           m_foundWindowToFocus;\n\n    // used for warping back after non-mouse input\n    Vector2D m_lastMousePos   = {};\n    double   m_mousePosDelta  = 0;\n    bool     m_lastInputMouse = true;\n\n    // for holding focus on buttons held\n    bool m_focusHeldByButtons   = false;\n    bool m_refocusHeldByButtons = false;\n\n    // for releasing mouse buttons\n    std::list<uint32_t> m_currentlyHeldButtons;\n\n    // idle inhibitors\n    struct SIdleInhibitor {\n        SP<CIdleInhibitor>  inhibitor;\n        bool                nonDesktop = false;\n        CHyprSignalListener surfaceDestroyListener;\n    };\n    std::vector<UP<SIdleInhibitor>> m_idleInhibitors;\n\n    void                            setBorderCursorIcon(eBorderIconDirection);\n    void                            setCursorIconOnBorder(PHLWINDOW w);\n\n    // cursor surface\n    struct {\n        bool                          hidden = false; // null surface = hidden\n        SP<Desktop::View::CWLSurface> wlSurface;\n        Vector2D                      vHotspot;\n        std::string                   name; // if not empty, means set by name.\n    } m_cursorSurfaceInfo;\n\n    void restoreCursorIconToApp(); // no-op if restored\n\n    // discrete scrolling emulation using v120 data\n    struct {\n        bool     lastEventSign     = false;\n        bool     lastEventAxis     = false;\n        uint32_t lastEventTime     = 0;\n        uint32_t accumulatedScroll = 0;\n    } m_scrollWheelState;\n    bool                  m_pointerAxisFramePending = false;\n\n    bool                  shareKeyFromAllKBs(uint32_t key, bool pressed);\n    uint32_t              shareModsFromAllKBs(uint32_t depressed);\n    std::vector<uint32_t> m_pressed;\n    uint32_t              m_lastMods = 0;\n\n    friend class CKeybindManager;\n    friend class Desktop::View::CWLSurface;\n    friend class CWorkspaceSwipeGesture;\n};\n\ninline UP<CInputManager> g_pInputManager;\n"
  },
  {
    "path": "src/managers/input/InputMethodPopup.cpp",
    "content": "#include \"InputMethodPopup.hpp\"\n#include \"InputManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../protocols/FractionalScale.hpp\"\n#include \"../../protocols/InputMethodV2.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../render/Renderer.hpp\"\n\nCInputPopup::CInputPopup(SP<CInputMethodPopupV2> popup_) : m_popup(popup_) {\n    m_listeners.commit  = popup_->m_events.commit.listen([this] { onCommit(); });\n    m_listeners.map     = popup_->m_events.map.listen([this] { onMap(); });\n    m_listeners.unmap   = popup_->m_events.unmap.listen([this] { onUnmap(); });\n    m_listeners.destroy = popup_->m_events.destroy.listen([this] { onDestroy(); });\n    m_surface           = Desktop::View::CWLSurface::create();\n    m_surface->assign(popup_->surface());\n}\n\nSP<Desktop::View::CWLSurface> CInputPopup::queryOwner() {\n    const auto FOCUSED = g_pInputManager->m_relay.getFocusedTextInput();\n\n    if (!FOCUSED)\n        return nullptr;\n\n    return Desktop::View::CWLSurface::fromResource(FOCUSED->focusedSurface());\n}\n\nvoid CInputPopup::onDestroy() {\n    g_pInputManager->m_relay.removePopup(this);\n}\n\nvoid CInputPopup::onMap() {\n    Log::logger->log(Log::DEBUG, \"Mapped an IME Popup\");\n\n    updateBox();\n    damageEntire();\n\n    const auto PMONITOR = g_pCompositor->getMonitorFromVector(globalBox().middle());\n\n    if (!PMONITOR)\n        return;\n\n    PROTO::fractional->sendScale(m_surface->resource(), PMONITOR->m_scale);\n}\n\nvoid CInputPopup::onUnmap() {\n    Log::logger->log(Log::DEBUG, \"Unmapped an IME Popup\");\n\n    damageEntire();\n}\n\nvoid CInputPopup::onCommit() {\n    updateBox();\n}\n\nvoid CInputPopup::damageEntire() {\n    const auto OWNER = queryOwner();\n\n    if (!OWNER) {\n        Log::logger->log(Log::ERR, \"BUG THIS: No owner in imepopup::damageentire\");\n        return;\n    }\n    CBox box = globalBox();\n    g_pHyprRenderer->damageBox(box);\n}\n\nvoid CInputPopup::damageSurface() {\n    const auto OWNER = queryOwner();\n\n    if (!OWNER) {\n        Log::logger->log(Log::ERR, \"BUG THIS: No owner in imepopup::damagesurface\");\n        return;\n    }\n\n    Vector2D pos = globalBox().pos();\n    g_pHyprRenderer->damageSurface(m_surface->resource(), pos.x, pos.y);\n}\n\nvoid CInputPopup::updateBox() {\n    if (!m_popup->m_mapped)\n        return;\n\n    const auto OWNER      = queryOwner();\n    const auto PFOCUSEDTI = g_pInputManager->m_relay.getFocusedTextInput();\n\n    if (!PFOCUSEDTI)\n        return;\n\n    bool cursorRect      = PFOCUSEDTI->hasCursorRectangle();\n    CBox cursorBoxParent = PFOCUSEDTI->cursorBox();\n\n    CBox parentBox;\n\n    if (!OWNER)\n        parentBox = {0, 0, 500, 500};\n    else\n        parentBox = OWNER->getSurfaceBoxGlobal().value_or(CBox{0, 0, 500, 500});\n\n    if (!cursorRect) {\n        Vector2D coords = OWNER ? OWNER->getSurfaceBoxGlobal().value_or(CBox{0, 0, 500, 500}).pos() : Vector2D{0, 0};\n        parentBox       = {coords, {500, 500}};\n        cursorBoxParent = {0, 0, sc<int>(parentBox.w), sc<int>(parentBox.h)};\n    }\n\n    Vector2D   currentPopupSize = m_surface->getViewporterCorrectedSize() / m_surface->resource()->m_current.scale;\n\n    PHLMONITOR pMonitor = g_pCompositor->getMonitorFromVector(parentBox.middle());\n\n    Vector2D   popupOffset(0, 0);\n\n    if (parentBox.y + cursorBoxParent.y + cursorBoxParent.height + currentPopupSize.y > pMonitor->m_position.y + pMonitor->m_size.y)\n        popupOffset.y -= currentPopupSize.y;\n    else\n        popupOffset.y = cursorBoxParent.height;\n\n    double popupOverflow = parentBox.x + cursorBoxParent.x + currentPopupSize.x - (pMonitor->m_position.x + pMonitor->m_size.x);\n    if (popupOverflow > 0)\n        popupOffset.x -= popupOverflow;\n\n    CBox cursorBoxLocal({-popupOffset.x, -popupOffset.y}, cursorBoxParent.size());\n    m_popup->sendInputRectangle(cursorBoxLocal);\n\n    CBox       popupBoxParent(cursorBoxParent.pos() + popupOffset, currentPopupSize);\n    const bool boxChanged = popupBoxParent != m_lastBoxLocal;\n    if (boxChanged)\n        damageEntire(); // damage the old location before updating\n\n    m_lastBoxLocal = popupBoxParent;\n\n    // Since a redraw request is not always sent when only the position is updated,\n    // a manual redraw may be required in some cases.\n    if (boxChanged)\n        damageEntire();\n\n    damageSurface();\n\n    if (const auto PM = g_pCompositor->getMonitorFromCursor(); PM && PM->m_id != m_lastMonitor) {\n        const auto PML = g_pCompositor->getMonitorFromID(m_lastMonitor);\n\n        if (PML)\n            m_surface->resource()->leave(PML->m_self.lock());\n\n        m_surface->resource()->enter(PM->m_self.lock());\n\n        m_lastMonitor = PM->m_id;\n    }\n}\n\nCBox CInputPopup::globalBox() {\n    const auto OWNER = queryOwner();\n\n    if (!OWNER) {\n        Log::logger->log(Log::ERR, \"BUG THIS: No owner in imepopup::globalbox\");\n        return {};\n    }\n    CBox parentBox = OWNER->getSurfaceBoxGlobal().value_or(CBox{0, 0, 500, 500});\n\n    return m_lastBoxLocal.copy().translate(parentBox.pos());\n}\n\nbool CInputPopup::isVecInPopup(const Vector2D& point) {\n    return globalBox().containsPoint(point);\n}\n\nSP<CWLSurfaceResource> CInputPopup::getSurface() {\n    return m_surface->resource();\n}\n"
  },
  {
    "path": "src/managers/input/InputMethodPopup.hpp",
    "content": "#pragma once\n\n#include \"../../desktop/view/WLSurface.hpp\"\n#include \"../../macros.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n\nclass CInputMethodPopupV2;\n\nclass CInputPopup {\n  public:\n    CInputPopup(SP<CInputMethodPopupV2> popup);\n\n    void                   damageEntire();\n    void                   damageSurface();\n\n    bool                   isVecInPopup(const Vector2D& point);\n\n    CBox                   globalBox();\n    SP<CWLSurfaceResource> getSurface();\n\n    void                   onCommit();\n\n  private:\n    SP<Desktop::View::CWLSurface> queryOwner();\n    void                          updateBox();\n\n    void                          onDestroy();\n    void                          onMap();\n    void                          onUnmap();\n\n    WP<CInputMethodPopupV2>       m_popup;\n    SP<Desktop::View::CWLSurface> m_surface;\n    CBox                          m_lastBoxLocal;\n    MONITORID                     m_lastMonitor = MONITOR_INVALID;\n\n    struct {\n        CHyprSignalListener map;\n        CHyprSignalListener unmap;\n        CHyprSignalListener destroy;\n        CHyprSignalListener commit;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/managers/input/InputMethodRelay.cpp",
    "content": "#include \"InputMethodRelay.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../event/EventBus.hpp\"\n#include \"../../protocols/TextInputV3.hpp\"\n#include \"../../protocols/TextInputV1.hpp\"\n#include \"../../protocols/InputMethodV2.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n\nCInputMethodRelay::CInputMethodRelay() {\n    static auto P = Event::bus()->m_events.input.keyboard.focus.listen([&](SP<CWLSurfaceResource> surf) { onKeyboardFocus(surf); });\n\n    m_listeners.newTIV3 = PROTO::textInputV3->m_events.newTextInput.listen([this](const auto& input) { onNewTextInput(input); });\n    m_listeners.newTIV1 = PROTO::textInputV1->m_events.newTextInput.listen([this](const auto& input) { onNewTextInput(input); });\n    m_listeners.newIME  = PROTO::ime->m_events.newIME.listen([this](const auto& ime) { onNewIME(ime); });\n}\n\nvoid CInputMethodRelay::onNewIME(SP<CInputMethodV2> pIME) {\n    if (!m_inputMethod.expired()) {\n        Log::logger->log(Log::ERR, \"Cannot register 2 IMEs at once!\");\n\n        pIME->unavailable();\n\n        return;\n    }\n\n    m_inputMethod = pIME;\n\n    m_listeners.commitIME = pIME->m_events.onCommit.listen([this] {\n        const auto PTI = getFocusedTextInput();\n\n        if (!PTI) {\n            Log::logger->log(Log::DEBUG, \"No focused TextInput on IME Commit\");\n            return;\n        }\n\n        PTI->updateIMEState(m_inputMethod.lock());\n    });\n\n    m_listeners.destroyIME = pIME->m_events.destroy.listen([this] {\n        const auto PTI = getFocusedTextInput();\n\n        Log::logger->log(Log::DEBUG, \"IME Destroy\");\n\n        if (PTI)\n            PTI->leave();\n\n        m_inputMethod.reset();\n    });\n\n    m_listeners.newPopup = pIME->m_events.newPopup.listen([this](const SP<CInputMethodPopupV2>& popup) {\n        m_inputMethodPopups.emplace_back(makeUnique<CInputPopup>(popup));\n        Log::logger->log(Log::DEBUG, \"New input popup\");\n    });\n\n    if (!Desktop::focusState()->surface())\n        return;\n\n    for (auto const& ti : m_textInputs) {\n        if (ti->client() != Desktop::focusState()->surface()->client())\n            continue;\n\n        if (ti->isV3())\n            ti->enter(Desktop::focusState()->surface());\n        else\n            ti->onEnabled(Desktop::focusState()->surface());\n    }\n}\n\nvoid CInputMethodRelay::removePopup(CInputPopup* pPopup) {\n    std::erase_if(m_inputMethodPopups, [pPopup](const auto& other) { return other.get() == pPopup; });\n}\n\nCTextInput* CInputMethodRelay::getFocusedTextInput() {\n    if (!Desktop::focusState()->surface())\n        return nullptr;\n\n    for (auto const& ti : m_textInputs) {\n        if (ti->focusedSurface() == Desktop::focusState()->surface() && ti->isEnabled())\n            return ti.get();\n    }\n\n    for (auto const& ti : m_textInputs) {\n        if (ti->focusedSurface() == Desktop::focusState()->surface())\n            return ti.get();\n    }\n\n    return nullptr;\n}\n\nvoid CInputMethodRelay::onNewTextInput(WP<CTextInputV3> tiv3) {\n    m_textInputs.emplace_back(makeUnique<CTextInput>(tiv3));\n}\n\nvoid CInputMethodRelay::onNewTextInput(WP<CTextInputV1> pTIV1) {\n    m_textInputs.emplace_back(makeUnique<CTextInput>(pTIV1));\n}\n\nvoid CInputMethodRelay::removeTextInput(CTextInput* pInput) {\n    std::erase_if(m_textInputs, [pInput](const auto& other) { return other.get() == pInput; });\n}\n\nvoid CInputMethodRelay::updateAllPopups() {\n    for (auto const& p : m_inputMethodPopups) {\n        p->onCommit();\n    }\n}\n\nvoid CInputMethodRelay::activateIME(CTextInput* pInput, bool shouldCommit) {\n    if (m_inputMethod.expired())\n        return;\n\n    m_inputMethod->activate();\n    if (shouldCommit)\n        commitIMEState(pInput);\n}\n\nvoid CInputMethodRelay::deactivateIME(CTextInput* pInput, bool shouldCommit) {\n    if (m_inputMethod.expired())\n        return;\n\n    m_inputMethod->deactivate();\n    if (shouldCommit)\n        commitIMEState(pInput);\n}\n\nvoid CInputMethodRelay::commitIMEState(CTextInput* pInput) {\n    if (m_inputMethod.expired())\n        return;\n\n    pInput->commitStateToIME(m_inputMethod.lock());\n}\n\nvoid CInputMethodRelay::onKeyboardFocus(SP<CWLSurfaceResource> pSurface) {\n    if (m_inputMethod.expired())\n        return;\n\n    if (pSurface == m_lastKbFocus)\n        return;\n\n    m_lastKbFocus = pSurface;\n\n    for (auto const& ti : m_textInputs) {\n        if (!ti->focusedSurface())\n            continue;\n\n        ti->leave();\n    }\n\n    if (!pSurface)\n        return;\n\n    for (auto const& ti : m_textInputs) {\n        if (!ti->isV3())\n            continue;\n\n        if (ti->client() != pSurface->client())\n            continue;\n\n        ti->enter(pSurface);\n    }\n}\n\nCInputPopup* CInputMethodRelay::popupFromCoords(const Vector2D& point) {\n    for (auto const& p : m_inputMethodPopups) {\n        if (p->isVecInPopup(point))\n            return p.get();\n    }\n\n    return nullptr;\n}\n\nCInputPopup* CInputMethodRelay::popupFromSurface(const SP<CWLSurfaceResource> surface) {\n    for (auto const& p : m_inputMethodPopups) {\n        if (p->getSurface() == surface)\n            return p.get();\n    }\n\n    return nullptr;\n}\n"
  },
  {
    "path": "src/managers/input/InputMethodRelay.hpp",
    "content": "#pragma once\n\n#include <list>\n#include \"../../defines.hpp\"\n#include \"../../helpers/WLClasses.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"TextInput.hpp\"\n#include \"InputMethodPopup.hpp\"\n#include <any>\n\nclass CInputManager;\nclass IHyprRenderer;\nclass CTextInputV1;\nclass CInputMethodV2;\n\nclass CInputMethodRelay {\n  public:\n    CInputMethodRelay();\n\n    void               onNewIME(SP<CInputMethodV2>);\n    void               onNewTextInput(WP<CTextInputV3> tiv3);\n    void               onNewTextInput(WP<CTextInputV1> pTIV1);\n\n    void               activateIME(CTextInput* pInput, bool shouldCommit = true);\n    void               deactivateIME(CTextInput* pInput, bool shouldCommit = true);\n    void               commitIMEState(CTextInput* pInput);\n    void               removeTextInput(CTextInput* pInput);\n\n    void               onKeyboardFocus(SP<CWLSurfaceResource>);\n\n    CTextInput*        getFocusedTextInput();\n\n    void               removePopup(CInputPopup*);\n\n    CInputPopup*       popupFromCoords(const Vector2D& point);\n    CInputPopup*       popupFromSurface(const SP<CWLSurfaceResource> surface);\n\n    void               updateAllPopups();\n\n    WP<CInputMethodV2> m_inputMethod;\n\n  private:\n    std::vector<UP<CTextInput>>  m_textInputs;\n    std::vector<UP<CInputPopup>> m_inputMethodPopups;\n\n    WP<CWLSurfaceResource>       m_lastKbFocus;\n\n    struct {\n        CHyprSignalListener newTIV3;\n        CHyprSignalListener newTIV1;\n        CHyprSignalListener newIME;\n        CHyprSignalListener commitIME;\n        CHyprSignalListener destroyIME;\n        CHyprSignalListener newPopup;\n    } m_listeners;\n\n    friend class IHyprRenderer;\n    friend class CInputManager;\n    friend class CTextInputV1ProtocolManager;\n    friend class CTextInput;\n};\n"
  },
  {
    "path": "src/managers/input/Tablets.cpp",
    "content": "#include \"InputManager.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../protocols/Tablet.hpp\"\n#include \"../../devices/Tablet.hpp\"\n#include \"../../managers/PointerManager.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../protocols/PointerConstraints.hpp\"\n#include \"../../protocols/core/DataDevice.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nstatic void unfocusTool(SP<CTabletTool> tool) {\n    if (!tool->getSurface())\n        return;\n\n    tool->setSurface(nullptr);\n    if (tool->m_isDown)\n        PROTO::tablet->up(tool);\n    for (auto const& b : tool->m_buttonsDown) {\n        PROTO::tablet->buttonTool(tool, b, false);\n    }\n    PROTO::tablet->proximityOut(tool);\n}\n\nstatic void focusTool(SP<CTabletTool> tool, SP<CTablet> tablet, SP<CWLSurfaceResource> surf) {\n    if (tool->getSurface() == surf || !surf)\n        return;\n\n    if (tool->getSurface() && tool->getSurface() != surf)\n        unfocusTool(tool);\n\n    tool->setSurface(surf);\n    PROTO::tablet->proximityIn(tool, tablet, surf);\n    if (tool->m_isDown)\n        PROTO::tablet->down(tool);\n    for (auto const& b : tool->m_buttonsDown) {\n        PROTO::tablet->buttonTool(tool, b, true);\n    }\n}\n\nstatic void refocusTablet(SP<CTablet> tab, SP<CTabletTool> tool, bool motion = false) {\n    const auto LASTHLSURFACE = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock());\n\n    if (!LASTHLSURFACE || !tool->m_active) {\n        if (tool->getSurface())\n            unfocusTool(tool);\n\n        return;\n    }\n\n    const auto BOX = LASTHLSURFACE->getSurfaceBoxGlobal();\n\n    if (!BOX.has_value()) {\n        if (tool->getSurface())\n            unfocusTool(tool);\n\n        return;\n    }\n\n    const auto CURSORPOS = g_pInputManager->getMouseCoordsInternal();\n\n    focusTool(tool, tab, g_pSeatManager->m_state.pointerFocus.lock());\n\n    if (!motion)\n        return;\n\n    const auto WINDOW = Desktop::View::CWindow::fromView(LASTHLSURFACE->view());\n\n    if (LASTHLSURFACE->constraint() && tool->aq()->type != Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_MOUSE) {\n        // cursor logic will completely break here as the cursor will be locked.\n        // let's just \"map\" the desired position to the constraint area.\n\n        Vector2D local;\n\n        // yes, this technically ignores any regions set by the app. Too bad!\n        if (WINDOW)\n            local = tool->m_absolutePos * WINDOW->m_realSize->goal();\n        else\n            local = tool->m_absolutePos * BOX->size();\n\n        if (WINDOW && WINDOW->m_isX11)\n            local = local * WINDOW->m_X11SurfaceScaledBy;\n\n        PROTO::tablet->motion(tool, local);\n        return;\n    }\n\n    auto local = CURSORPOS - BOX->pos();\n\n    if (WINDOW && WINDOW->m_isX11)\n        local = local * WINDOW->m_X11SurfaceScaledBy;\n\n    PROTO::tablet->motion(tool, local);\n}\n\nstatic Vector2D transformToActiveRegion(const Vector2D pos, const CBox activeArea) {\n    auto newPos = pos;\n\n    //Calculate transformations if active area is set\n    if (!activeArea.empty()) {\n        if (!std::isnan(pos.x))\n            newPos.x = (pos.x - activeArea.x) / (activeArea.w - activeArea.x);\n        if (!std::isnan(pos.y))\n            newPos.y = (pos.y - activeArea.y) / (activeArea.h - activeArea.y);\n    }\n\n    return newPos;\n}\n\nvoid CInputManager::onTabletAxis(CTablet::SAxisEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.tablet.axis.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    const auto PTAB  = e.tablet;\n    const auto PTOOL = ensureTabletToolPresent(e.tool);\n\n    if (PTOOL->m_active && (e.updatedAxes & (CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X | CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y))) {\n        double   x  = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X) ? e.axis.x : NAN;\n        double   dx = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X) ? e.axisDelta.x : NAN;\n        double   y  = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y) ? e.axis.y : NAN;\n        double   dy = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y) ? e.axisDelta.y : NAN;\n\n        Vector2D delta = {std::isnan(dx) ? 0.0 : dx, std::isnan(dy) ? 0.0 : dy};\n\n        switch (e.tool->type) {\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_MOUSE: {\n                g_pPointerManager->move(delta);\n                break;\n            }\n            default: {\n                if (!std::isnan(x))\n                    PTOOL->m_absolutePos.x = x;\n                if (!std::isnan(y))\n                    PTOOL->m_absolutePos.y = y;\n\n                if (PTAB->m_relativeInput)\n                    g_pPointerManager->move(delta);\n                else\n                    g_pPointerManager->warpAbsolute(transformToActiveRegion({x, y}, PTAB->m_activeArea), PTAB);\n\n                break;\n            }\n        }\n\n        m_lastInputTouch = false;\n        if (!PTOOL->m_isDown || PROTO::data->dndActive())\n            simulateMouseMovement();\n        refocusTablet(PTAB, PTOOL, true);\n        m_lastCursorMovement.reset();\n    }\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_PRESSURE)\n        PROTO::tablet->pressure(PTOOL, e.pressure);\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_DISTANCE)\n        PROTO::tablet->distance(PTOOL, e.distance);\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_ROTATION)\n        PROTO::tablet->rotation(PTOOL, e.rotation);\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_SLIDER)\n        PROTO::tablet->slider(PTOOL, e.slider);\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_WHEEL)\n        PROTO::tablet->wheel(PTOOL, e.wheelDelta);\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X)\n        PTOOL->m_tilt.x = e.tilt.x;\n\n    if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y)\n        PTOOL->m_tilt.y = e.tilt.y;\n\n    if (e.updatedAxes & (CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X | CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y))\n        PROTO::tablet->tilt(PTOOL, PTOOL->m_tilt);\n}\n\nvoid CInputManager::onTabletTip(CTablet::STipEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.tablet.tip.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    const auto PTAB  = e.tablet;\n    const auto PTOOL = ensureTabletToolPresent(e.tool);\n    const auto POS   = e.tip;\n\n    if (PTAB->m_relativeInput)\n        g_pPointerManager->move({0, 0});\n    else\n        g_pPointerManager->warpAbsolute(transformToActiveRegion(POS, PTAB->m_activeArea), PTAB);\n\n    if (e.in)\n        refocus();\n\n    refocusTablet(PTAB, PTOOL, true);\n\n    if (e.in)\n        PROTO::tablet->down(PTOOL);\n    else\n        PROTO::tablet->up(PTOOL);\n\n    PTOOL->m_isDown = e.in;\n}\n\nvoid CInputManager::onTabletButton(CTablet::SButtonEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.tablet.button.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    const auto PTOOL = ensureTabletToolPresent(e.tool);\n\n    if (e.down)\n        refocus();\n\n    PROTO::tablet->buttonTool(PTOOL, e.button, e.down);\n\n    if (e.down)\n        PTOOL->m_buttonsDown.push_back(e.button);\n    else\n        std::erase(PTOOL->m_buttonsDown, e.button);\n}\n\nvoid CInputManager::onTabletProximity(CTablet::SProximityEvent e) {\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.tablet.proximity.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    const auto PTAB  = e.tablet;\n    const auto PTOOL = ensureTabletToolPresent(e.tool);\n\n    PTOOL->m_active = e.in;\n\n    if (!e.in) {\n        m_lastInputTablet = false;\n        if (PTOOL->getSurface())\n            unfocusTool(PTOOL);\n    } else {\n        m_lastInputTablet = true;\n        simulateMouseMovement();\n        refocusTablet(PTAB, PTOOL);\n    }\n}\n\nvoid CInputManager::newTablet(SP<Aquamarine::ITablet> pDevice) {\n    const auto PNEWTABLET = m_tablets.emplace_back(CTablet::create(pDevice));\n    m_hids.emplace_back(PNEWTABLET);\n\n    try {\n        PNEWTABLET->m_hlName = g_pInputManager->getNameForNewDevice(pDevice->getName());\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Tablet had no name???\"); // logic error\n    }\n\n    g_pPointerManager->attachTablet(PNEWTABLET);\n\n    PNEWTABLET->m_events.destroy.listenStatic([this, tablet = PNEWTABLET.get()] {\n        auto TABLET = tablet->m_self;\n        destroyTablet(TABLET.lock());\n    });\n\n    setTabletConfigs();\n}\n\nSP<CTabletTool> CInputManager::ensureTabletToolPresent(SP<Aquamarine::ITabletTool> pTool) {\n\n    for (auto const& t : m_tabletTools) {\n        if (t->aq() == pTool)\n            return t;\n    }\n\n    const auto PTOOL = m_tabletTools.emplace_back(CTabletTool::create(pTool));\n    m_hids.emplace_back(PTOOL);\n\n    try {\n        PTOOL->m_hlName = g_pInputManager->getNameForNewDevice(pTool->getName());\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Tablet had no name???\"); // logic error\n    }\n\n    PTOOL->m_events.destroy.listenStatic([this, tool = PTOOL.get()] {\n        auto TOOL = tool->m_self;\n        destroyTabletTool(TOOL.lock());\n    });\n\n    return PTOOL;\n}\n\nvoid CInputManager::newTabletPad(SP<Aquamarine::ITabletPad> pDevice) {\n    const auto PNEWPAD = m_tabletPads.emplace_back(CTabletPad::create(pDevice));\n    m_hids.emplace_back(PNEWPAD);\n\n    try {\n        PNEWPAD->m_hlName = g_pInputManager->getNameForNewDevice(pDevice->getName());\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"Pad had no name???\"); // logic error\n    }\n\n    PNEWPAD->m_events.destroy.listenStatic([this, pad = PNEWPAD.get()] {\n        auto PAD = pad->m_self;\n        destroyTabletPad(PAD.lock());\n    });\n\n    PNEWPAD->m_padEvents.button.listenStatic([pad = PNEWPAD.get()](const CTabletPad::SButtonEvent& event) {\n        const auto PPAD = pad->m_self.lock();\n\n        PROTO::tablet->mode(PPAD, 0, event.mode, event.timeMs);\n        PROTO::tablet->buttonPad(PPAD, event.button, event.timeMs, event.down);\n    });\n\n    PNEWPAD->m_padEvents.strip.listenStatic([pad = PNEWPAD.get()](const CTabletPad::SStripEvent& event) {\n        const auto PPAD = pad->m_self.lock();\n        PROTO::tablet->strip(PPAD, event.strip, event.position, event.finger, event.timeMs);\n    });\n\n    PNEWPAD->m_padEvents.ring.listenStatic([pad = PNEWPAD.get()](const CTabletPad::SRingEvent& event) {\n        const auto PPAD = pad->m_self.lock();\n        PROTO::tablet->ring(PPAD, event.ring, event.position, event.finger, event.timeMs);\n    });\n\n    PNEWPAD->m_padEvents.attach.listenStatic([pad = PNEWPAD.get()](const SP<CTabletTool>& tool) { pad->m_parent = tool; });\n}\n"
  },
  {
    "path": "src/managers/input/TextInput.cpp",
    "content": "#include \"TextInput.hpp\"\n#include \"InputManager.hpp\"\n#include \"../../protocols/TextInputV1.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../protocols/TextInputV3.hpp\"\n#include \"../../protocols/InputMethodV2.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n\nCTextInput::CTextInput(WP<CTextInputV1> ti) : m_v1Input(ti) {\n    initCallbacks();\n}\n\nCTextInput::CTextInput(WP<CTextInputV3> ti) : m_v3Input(ti) {\n    initCallbacks();\n}\n\nvoid CTextInput::initCallbacks() {\n    if (isV3()) {\n        const auto INPUT = m_v3Input.lock();\n\n        m_listeners.enable  = INPUT->m_events.enable.listen([this] { onEnabled(); });\n        m_listeners.disable = INPUT->m_events.disable.listen([this] { onDisabled(); });\n        m_listeners.commit  = INPUT->m_events.onCommit.listen([this] { onCommit(); });\n        m_listeners.reset   = INPUT->m_events.reset.listen([this] { onReset(); });\n        m_listeners.destroy = INPUT->m_events.destroy.listen([this] { destroy(); });\n\n        if (Desktop::focusState()->surface() && Desktop::focusState()->surface()->client() == INPUT->client())\n            enter(Desktop::focusState()->surface());\n    } else {\n        const auto INPUT = m_v1Input.lock();\n\n        m_listeners.enable  = INPUT->m_events.enable.listen([this](const auto& surface) { onEnabled(surface); });\n        m_listeners.disable = INPUT->m_events.disable.listen([this] { onDisabled(); });\n        m_listeners.commit  = INPUT->m_events.onCommit.listen([this] { onCommit(); });\n        m_listeners.reset   = INPUT->m_events.reset.listen([this] { onReset(); });\n        m_listeners.destroy = INPUT->m_events.destroy.listen([this] { destroy(); });\n    }\n}\n\nvoid CTextInput::destroy() {\n    m_listeners.surfaceUnmap.reset();\n    m_listeners.surfaceDestroy.reset();\n\n    g_pInputManager->m_relay.removeTextInput(this);\n\n    if (!g_pInputManager->m_relay.getFocusedTextInput())\n        g_pInputManager->m_relay.deactivateIME(nullptr, false);\n}\n\nvoid CTextInput::onEnabled(SP<CWLSurfaceResource> surfV1) {\n    Log::logger->log(Log::DEBUG, \"TI ENABLE\");\n\n    if (g_pInputManager->m_relay.m_inputMethod.expired()) {\n        // Log::logger->log(Log::WARN,  \"Enabling TextInput on no IME!\");\n        return;\n    }\n\n    // v1 only, map surface to PTI\n    if (!isV3()) {\n        if (Desktop::focusState()->surface() != surfV1 || !m_v1Input->m_active)\n            return;\n\n        enter(surfV1);\n    }\n\n    g_pInputManager->m_relay.activateIME(this);\n}\n\nvoid CTextInput::onDisabled() {\n    if (g_pInputManager->m_relay.m_inputMethod.expired()) {\n        //  Log::logger->log(Log::WARN,  \"Disabling TextInput on no IME!\");\n        return;\n    }\n\n    if (!isV3())\n        leave();\n\n    m_listeners.surfaceUnmap.reset();\n    m_listeners.surfaceDestroy.reset();\n\n    if (!focusedSurface())\n        return;\n\n    const auto PFOCUSEDTI = g_pInputManager->m_relay.getFocusedTextInput();\n    if (!PFOCUSEDTI || PFOCUSEDTI != this)\n        return;\n\n    g_pInputManager->m_relay.deactivateIME(this);\n}\n\nvoid CTextInput::onReset() {\n    if (g_pInputManager->m_relay.m_inputMethod.expired())\n        return;\n\n    if (!focusedSurface())\n        return;\n\n    const auto PFOCUSEDTI = g_pInputManager->m_relay.getFocusedTextInput();\n    if (!PFOCUSEDTI || PFOCUSEDTI != this)\n        return;\n\n    g_pInputManager->m_relay.deactivateIME(this, false);\n    g_pInputManager->m_relay.activateIME(this);\n}\n\nvoid CTextInput::onCommit() {\n    if (g_pInputManager->m_relay.m_inputMethod.expired()) {\n        //   Log::logger->log(Log::WARN,  \"Committing TextInput on no IME!\");\n        return;\n    }\n\n    if (!(isV3() ? m_v3Input->m_current.enabled.value : m_v1Input->m_active)) {\n        Log::logger->log(Log::WARN, \"Disabled TextInput commit?\");\n        return;\n    }\n\n    g_pInputManager->m_relay.commitIMEState(this);\n}\n\nvoid CTextInput::setFocusedSurface(SP<CWLSurfaceResource> pSurface) {\n    if (pSurface == m_focusedSurface)\n        return;\n\n    m_focusedSurface = pSurface;\n\n    if (!pSurface)\n        return;\n\n    m_listeners.surfaceUnmap.reset();\n    m_listeners.surfaceDestroy.reset();\n\n    m_listeners.surfaceUnmap = pSurface->m_events.unmap.listen([this] {\n        Log::logger->log(Log::DEBUG, \"Unmap TI owner1\");\n\n        if (m_enterLocks)\n            m_enterLocks--;\n        m_focusedSurface.reset();\n        m_listeners.surfaceUnmap.reset();\n        m_listeners.surfaceDestroy.reset();\n\n        if (isV3() && !m_v3Input.expired() && m_v3Input->m_current.enabled.value) {\n            m_v3Input->m_pending.enabled.value            = false;\n            m_v3Input->m_pending.enabled.isDisablePending = false;\n            m_v3Input->m_pending.enabled.isEnablePending  = false;\n            m_v3Input->m_current.enabled.value            = false;\n        }\n\n        if (!g_pInputManager->m_relay.getFocusedTextInput())\n            g_pInputManager->m_relay.deactivateIME(this);\n    });\n\n    m_listeners.surfaceDestroy = pSurface->m_events.destroy.listen([this] {\n        Log::logger->log(Log::DEBUG, \"Destroy TI owner1\");\n\n        if (m_enterLocks)\n            m_enterLocks--;\n        m_focusedSurface.reset();\n        m_listeners.surfaceUnmap.reset();\n        m_listeners.surfaceDestroy.reset();\n\n        if (isV3() && !m_v3Input.expired() && m_v3Input->m_current.enabled.value) {\n            m_v3Input->m_pending.enabled.value            = false;\n            m_v3Input->m_pending.enabled.isDisablePending = false;\n            m_v3Input->m_pending.enabled.isEnablePending  = false;\n            m_v3Input->m_current.enabled.value            = false;\n        }\n\n        if (!g_pInputManager->m_relay.getFocusedTextInput())\n            g_pInputManager->m_relay.deactivateIME(this);\n    });\n}\n\nbool CTextInput::isV3() {\n    return m_v3Input && !m_v1Input;\n}\n\nvoid CTextInput::enter(SP<CWLSurfaceResource> pSurface) {\n    if (!pSurface || !pSurface->m_mapped)\n        return;\n\n    if (pSurface == focusedSurface())\n        return;\n\n    if (focusedSurface())\n        leave();\n\n    m_enterLocks++;\n    if (m_enterLocks != 1) {\n        Log::logger->log(Log::ERR, \"BUG THIS: TextInput has != 1 locks in enter\");\n        leave();\n        m_enterLocks = 1;\n    }\n\n    if (isV3())\n        m_v3Input->enter(pSurface);\n    else {\n        m_v1Input->enter(pSurface);\n    }\n\n    setFocusedSurface(pSurface);\n}\n\nvoid CTextInput::leave() {\n    if (!focusedSurface())\n        return;\n\n    m_enterLocks--;\n    if (m_enterLocks != 0) {\n        Log::logger->log(Log::ERR, \"BUG THIS: TextInput has != 0 locks in leave\");\n        m_enterLocks = 0;\n    }\n\n    if (isV3())\n        m_v3Input->leave(focusedSurface());\n    else\n        m_v1Input->leave();\n\n    setFocusedSurface(nullptr);\n\n    g_pInputManager->m_relay.deactivateIME(this);\n}\n\nSP<CWLSurfaceResource> CTextInput::focusedSurface() {\n    return m_focusedSurface.lock();\n}\n\nwl_client* CTextInput::client() {\n    return isV3() ? m_v3Input->client() : m_v1Input->client();\n}\n\nvoid CTextInput::commitStateToIME(SP<CInputMethodV2> ime) {\n    if (isV3() && !m_v3Input.expired()) {\n        const auto INPUT = m_v3Input.lock();\n\n        if (INPUT->m_current.surrounding.updated)\n            ime->surroundingText(INPUT->m_current.surrounding.text, INPUT->m_current.surrounding.cursor, INPUT->m_current.surrounding.anchor);\n\n        ime->textChangeCause(INPUT->m_current.cause);\n\n        if (INPUT->m_current.contentType.updated)\n            ime->textContentType(INPUT->m_current.contentType.hint, INPUT->m_current.contentType.purpose);\n    } else if (!m_v1Input.expired()) {\n        const auto INPUT = m_v1Input.lock();\n\n        if (INPUT->m_pendingSurrounding.isPending)\n            ime->surroundingText(INPUT->m_pendingSurrounding.text, INPUT->m_pendingSurrounding.cursor, INPUT->m_pendingSurrounding.anchor);\n\n        ime->textChangeCause(ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD);\n\n        if (m_v1Input->m_pendingContentType.isPending)\n            ime->textContentType(sc<zwpTextInputV3ContentHint>(INPUT->m_pendingContentType.hint), sc<zwpTextInputV3ContentPurpose>(INPUT->m_pendingContentType.purpose));\n    }\n\n    g_pInputManager->m_relay.updateAllPopups();\n\n    ime->done();\n}\n\nvoid CTextInput::updateIMEState(SP<CInputMethodV2> ime) {\n    if (isV3()) {\n        const auto INPUT = m_v3Input.lock();\n\n        if (ime->m_current.preeditString.committed)\n            INPUT->preeditString(ime->m_current.preeditString.string, ime->m_current.preeditString.begin, ime->m_current.preeditString.end);\n\n        if (ime->m_current.committedString.committed)\n            INPUT->commitString(ime->m_current.committedString.string);\n\n        if (ime->m_current.deleteSurrounding.committed)\n            INPUT->deleteSurroundingText(ime->m_current.deleteSurrounding.before, ime->m_current.deleteSurrounding.after);\n\n        INPUT->sendDone();\n    } else {\n        const auto INPUT = m_v1Input.lock();\n\n        if (ime->m_current.preeditString.committed) {\n            INPUT->preeditCursor(ime->m_current.preeditString.begin);\n            INPUT->preeditStyling(0, std::string(ime->m_current.preeditString.string).length(), ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT);\n            INPUT->preeditString(m_v1Input->m_serial, ime->m_current.preeditString.string.c_str(), \"\");\n        } else {\n            INPUT->preeditCursor(0);\n            INPUT->preeditStyling(0, 0, ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT);\n            INPUT->preeditString(m_v1Input->m_serial, \"\", \"\");\n        }\n\n        if (ime->m_current.committedString.committed)\n            INPUT->commitString(m_v1Input->m_serial, ime->m_current.committedString.string.c_str());\n\n        if (ime->m_current.deleteSurrounding.committed) {\n            INPUT->deleteSurroundingText(std::string(ime->m_current.preeditString.string).length() - ime->m_current.deleteSurrounding.before,\n                                         ime->m_current.deleteSurrounding.after + ime->m_current.deleteSurrounding.before);\n\n            if (ime->m_current.preeditString.committed)\n                INPUT->commitString(m_v1Input->m_serial, ime->m_current.preeditString.string.c_str());\n        }\n    }\n}\n\nbool CTextInput::hasCursorRectangle() {\n    return !isV3() || m_v3Input->m_current.box.updated;\n}\n\nCBox CTextInput::cursorBox() {\n    return CBox{isV3() ? m_v3Input->m_current.box.cursorBox : m_v1Input->m_cursorRectangle};\n}\n\nbool CTextInput::isEnabled() {\n    return isV3() ? m_v3Input->m_current.enabled.value : true;\n}\n"
  },
  {
    "path": "src/managers/input/TextInput.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n\nstruct wl_client;\n\nclass CTextInputV1;\nclass CTextInputV3;\nclass CInputMethodV2;\nclass CWLSurfaceResource;\n\nclass CTextInput {\n  public:\n    CTextInput(WP<CTextInputV3> ti);\n    CTextInput(WP<CTextInputV1> ti);\n\n    bool                   isV3();\n    void                   enter(SP<CWLSurfaceResource> pSurface);\n    void                   leave();\n    void                   tiV1Destroyed();\n    wl_client*             client();\n    void                   commitStateToIME(SP<CInputMethodV2> ime);\n    void                   updateIMEState(SP<CInputMethodV2> ime);\n\n    void                   onEnabled(SP<CWLSurfaceResource> surfV1 = nullptr);\n    void                   onDisabled();\n    void                   onCommit();\n    void                   onReset();\n\n    bool                   isEnabled();\n    bool                   hasCursorRectangle();\n    CBox                   cursorBox();\n\n    SP<CWLSurfaceResource> focusedSurface();\n\n  private:\n    void                   setFocusedSurface(SP<CWLSurfaceResource> pSurface);\n    void                   initCallbacks();\n\n    void                   destroy();\n\n    WP<CWLSurfaceResource> m_focusedSurface;\n    int                    m_enterLocks = 0;\n    WP<CTextInputV3>       m_v3Input;\n    WP<CTextInputV1>       m_v1Input;\n\n    struct {\n        CHyprSignalListener enable;\n        CHyprSignalListener disable;\n        CHyprSignalListener reset;\n        CHyprSignalListener commit;\n        CHyprSignalListener destroy;\n        CHyprSignalListener surfaceUnmap;\n        CHyprSignalListener surfaceDestroy;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/managers/input/Touch.cpp",
    "content": "#include \"InputManager.hpp\"\n#include \"../SessionLockManager.hpp\"\n#include \"../../protocols/SessionLock.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../desktop/view/LayerSurface.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../devices/ITouch.hpp\"\n#include \"../../event/EventBus.hpp\"\n#include \"../SeatManager.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"UnifiedWorkspaceSwipeGesture.hpp\"\n\nvoid CInputManager::onTouchDown(ITouch::SDownEvent e) {\n    m_lastInputTouch = true;\n\n    static auto PSWIPETOUCH  = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_touch\");\n    static auto PGAPSOUTDATA = CConfigValue<Hyprlang::CUSTOMTYPE>(\"general:gaps_out\");\n    auto* const PGAPSOUT     = sc<CCssGapData*>((PGAPSOUTDATA.ptr())->getData());\n    // TODO: WORKSPACERULE.gapsOut.value_or()\n    auto                 gapsOut     = *PGAPSOUT;\n    static auto          PBORDERSIZE = CConfigValue<Hyprlang::INT>(\"general:border_size\");\n    static auto          PSWIPEINVR  = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_touch_invert\");\n\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.touch.down.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : \"\");\n\n    PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor();\n\n    const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size);\n\n    refocus(TOUCH_COORDS);\n\n    if (PMONITOR != Desktop::focusState()->monitor())\n        Desktop::focusState()->rawMonitorFocus(PMONITOR);\n\n    if (m_clickBehavior == CLICKMODE_KILL) {\n        IPointer::SButtonEvent e;\n        e.state = WL_POINTER_BUTTON_STATE_PRESSED;\n        g_pInputManager->processMouseDownKill(e);\n        return;\n    }\n\n    // Don't propagate new touches when a workspace swipe is in progress.\n    if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) {\n        return;\n        // TODO: Don't swipe if you touched a floating window.\n    } else if (*PSWIPETOUCH && (m_foundLSToFocus.expired() || m_foundLSToFocus->m_layer <= 1) && !g_pSessionLockManager->isSessionLocked()) {\n        const auto   PWORKSPACE  = PMONITOR->m_activeWorkspace;\n        const auto   STYLE       = PWORKSPACE->m_renderOffset->getStyle();\n        const bool   VERTANIMS   = STYLE == \"slidevert\" || STYLE.starts_with(\"slidefadevert\");\n        const double TARGETLEFT  = ((VERTANIMS ? gapsOut.m_top : gapsOut.m_left) + *PBORDERSIZE) / (VERTANIMS ? PMONITOR->m_size.y : PMONITOR->m_size.x);\n        const double TARGETRIGHT = 1 - (((VERTANIMS ? gapsOut.m_bottom : gapsOut.m_right) + *PBORDERSIZE) / (VERTANIMS ? PMONITOR->m_size.y : PMONITOR->m_size.x));\n        const double POSITION    = (VERTANIMS ? e.pos.y : e.pos.x);\n        if (POSITION < TARGETLEFT || POSITION > TARGETRIGHT) {\n            g_pUnifiedWorkspaceSwipe->begin();\n            g_pUnifiedWorkspaceSwipe->m_touchID = e.touchID;\n            // Set the initial direction based on which edge you started from\n            if (POSITION > 0.5)\n                g_pUnifiedWorkspaceSwipe->m_initialDirection = *PSWIPEINVR ? -1 : 1;\n            else\n                g_pUnifiedWorkspaceSwipe->m_initialDirection = *PSWIPEINVR ? 1 : -1;\n            return;\n        }\n    }\n\n    // could have abovelock surface, thus only use lock if no ls found\n    if (g_pSessionLockManager->isSessionLocked() && m_foundLSToFocus.expired()) {\n        m_touchData.touchFocusLockSurface = g_pSessionLockManager->getSessionLockSurfaceForMonitor(PMONITOR->m_id);\n        if (!m_touchData.touchFocusLockSurface)\n            Log::logger->log(Log::WARN, \"The session is locked but can't find a lock surface\");\n        else\n            m_touchData.touchFocusSurface = m_touchData.touchFocusLockSurface->surface->surface();\n    } else {\n        m_touchData.touchFocusLockSurface.reset();\n        m_touchData.touchFocusWindow  = m_foundWindowToFocus;\n        m_touchData.touchFocusSurface = m_foundSurfaceToFocus;\n        m_touchData.touchFocusLS      = m_foundLSToFocus;\n    }\n\n    Vector2D local;\n\n    if (m_touchData.touchFocusLockSurface) {\n        local                          = TOUCH_COORDS - PMONITOR->m_position;\n        m_touchData.touchSurfaceOrigin = TOUCH_COORDS - local;\n    } else if (!m_touchData.touchFocusWindow.expired()) {\n        if (m_touchData.touchFocusWindow->m_isX11) {\n            local                          = (TOUCH_COORDS - m_touchData.touchFocusWindow->m_realPosition->goal()) * m_touchData.touchFocusWindow->m_X11SurfaceScaledBy;\n            m_touchData.touchSurfaceOrigin = m_touchData.touchFocusWindow->m_realPosition->goal();\n        } else {\n            g_pCompositor->vectorWindowToSurface(TOUCH_COORDS, m_touchData.touchFocusWindow.lock(), local);\n            m_touchData.touchSurfaceOrigin = TOUCH_COORDS - local;\n        }\n    } else if (!m_touchData.touchFocusLS.expired()) {\n        PHLLS    foundSurf;\n        Vector2D foundCoords;\n        auto     surf = g_pCompositor->vectorToLayerPopupSurface(TOUCH_COORDS, PMONITOR, &foundCoords, &foundSurf);\n        if (surf) {\n            local                         = foundCoords;\n            m_touchData.touchFocusSurface = surf;\n        } else\n            local = TOUCH_COORDS - m_touchData.touchFocusLS->m_geometry.pos();\n\n        m_touchData.touchSurfaceOrigin = TOUCH_COORDS - local;\n    } else\n        return; // oops, nothing found.\n\n    g_pSeatManager->sendTouchDown(m_touchData.touchFocusSurface.lock(), e.timeMs, e.touchID, local);\n}\n\nvoid CInputManager::onTouchUp(ITouch::SUpEvent e) {\n    m_lastInputTouch = true;\n\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.touch.up.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) {\n        // If there was a swipe from this finger, end it.\n        if (e.touchID == g_pUnifiedWorkspaceSwipe->m_touchID)\n            g_pUnifiedWorkspaceSwipe->end();\n        return;\n    }\n\n    if (m_touchData.touchFocusSurface)\n        g_pSeatManager->sendTouchUp(e.timeMs, e.touchID);\n}\n\nvoid CInputManager::onTouchMove(ITouch::SMotionEvent e) {\n    m_lastInputTouch = true;\n\n    m_lastCursorMovement.reset();\n\n    Event::SCallbackInfo info;\n    Event::bus()->m_events.input.touch.motion.emit(e, info);\n    if (info.cancelled)\n        return;\n\n    if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) {\n        // Do nothing if this is using a different finger.\n        if (e.touchID != g_pUnifiedWorkspaceSwipe->m_touchID)\n            return;\n\n        const auto  ANIMSTYLE     = g_pUnifiedWorkspaceSwipe->m_workspaceBegin->m_renderOffset->getStyle();\n        const bool  VERTANIMS     = ANIMSTYLE == \"slidevert\" || ANIMSTYLE.starts_with(\"slidefadevert\");\n        static auto PSWIPEINVR    = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_touch_invert\");\n        static auto PSWIPEDIST    = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_distance\");\n        const auto  SWIPEDISTANCE = std::clamp(*PSWIPEDIST, sc<int64_t>(1LL), sc<int64_t>(UINT32_MAX));\n        // Handle the workspace swipe if there is one\n        if (g_pUnifiedWorkspaceSwipe->m_initialDirection == -1) {\n            if (*PSWIPEINVR)\n                // go from 0 to -SWIPEDISTANCE\n                g_pUnifiedWorkspaceSwipe->update(SWIPEDISTANCE * ((VERTANIMS ? e.pos.y : e.pos.x) - 1));\n            else\n                // go from 0 to -SWIPEDISTANCE\n                g_pUnifiedWorkspaceSwipe->update(SWIPEDISTANCE * (-1 * (VERTANIMS ? e.pos.y : e.pos.x)));\n        } else if (*PSWIPEINVR)\n            // go from 0 to SWIPEDISTANCE\n            g_pUnifiedWorkspaceSwipe->update(SWIPEDISTANCE * (VERTANIMS ? e.pos.y : e.pos.x));\n        else\n            // go from 0 to SWIPEDISTANCE\n            g_pUnifiedWorkspaceSwipe->update(SWIPEDISTANCE * (1 - (VERTANIMS ? e.pos.y : e.pos.x)));\n        return;\n    }\n    if (m_touchData.touchFocusLockSurface) {\n        const auto PMONITOR     = g_pCompositor->getMonitorFromID(m_touchData.touchFocusLockSurface->iMonitorID);\n        const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size);\n        const auto LOCAL        = TOUCH_COORDS - PMONITOR->m_position;\n        g_pSeatManager->sendTouchMotion(e.timeMs, e.touchID, LOCAL);\n    } else if (validMapped(m_touchData.touchFocusWindow)) {\n        const auto PMONITOR     = m_touchData.touchFocusWindow->m_monitor.lock();\n        const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size);\n        auto       local        = TOUCH_COORDS - m_touchData.touchSurfaceOrigin;\n        if (m_touchData.touchFocusWindow->m_isX11)\n            local = local * m_touchData.touchFocusWindow->m_X11SurfaceScaledBy;\n\n        g_pSeatManager->sendTouchMotion(e.timeMs, e.touchID, local);\n    } else if (validMapped(m_touchData.touchFocusLS)) {\n        const auto PMONITOR     = m_touchData.touchFocusLS->m_monitor.lock();\n        const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size);\n        const auto LOCAL        = TOUCH_COORDS - m_touchData.touchSurfaceOrigin;\n\n        g_pSeatManager->sendTouchMotion(e.timeMs, e.touchID, LOCAL);\n    }\n}\n"
  },
  {
    "path": "src/managers/input/UnifiedWorkspaceSwipeGesture.cpp",
    "content": "#include \"UnifiedWorkspaceSwipeGesture.hpp\"\n\n#include \"../../Compositor.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"InputManager.hpp\"\n\nbool CUnifiedWorkspaceSwipeGesture::isGestureInProgress() {\n    return m_workspaceBegin;\n}\n\nvoid CUnifiedWorkspaceSwipeGesture::begin() {\n    if (isGestureInProgress())\n        return;\n\n    const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;\n\n    Log::logger->log(Log::DEBUG, \"CUnifiedWorkspaceSwipeGesture::begin: Starting a swipe from {}\", PWORKSPACE->m_name);\n\n    m_workspaceBegin = PWORKSPACE;\n    m_delta          = 0;\n    m_monitor        = Desktop::focusState()->monitor();\n    m_avgSpeed       = 0;\n    m_speedPoints    = 0;\n\n    if (PWORKSPACE->m_hasFullscreenWindow) {\n        for (auto const& ls : Desktop::focusState()->monitor()->m_layerSurfaceLayers[2]) {\n            *ls->m_alpha = 1.f;\n        }\n    }\n}\n\nvoid CUnifiedWorkspaceSwipeGesture::update(double delta) {\n    if (!isGestureInProgress())\n        return;\n\n    static auto  PSWIPEDIST             = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_distance\");\n    static auto  PSWIPENEW              = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_create_new\");\n    static auto  PSWIPEDIRLOCK          = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_direction_lock\");\n    static auto  PSWIPEDIRLOCKTHRESHOLD = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_direction_lock_threshold\");\n    static auto  PSWIPEFOREVER          = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_forever\");\n    static auto  PSWIPEUSER             = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_use_r\");\n    static auto  PWORKSPACEGAP          = CConfigValue<Hyprlang::INT>(\"general:gaps_workspaces\");\n\n    const auto   SWIPEDISTANCE = std::clamp(*PSWIPEDIST, sc<int64_t>(1LL), sc<int64_t>(UINT32_MAX));\n    const auto   XDISTANCE     = m_monitor->m_size.x + *PWORKSPACEGAP;\n    const auto   YDISTANCE     = m_monitor->m_size.y + *PWORKSPACEGAP;\n    const auto   ANIMSTYLE     = m_workspaceBegin->m_renderOffset->getStyle();\n    const bool   VERTANIMS     = ANIMSTYLE == \"slidevert\" || ANIMSTYLE.starts_with(\"slidefadevert\");\n    const double d             = m_delta - delta;\n    m_delta                    = delta;\n\n    m_avgSpeed = (m_avgSpeed * m_speedPoints + abs(d)) / (m_speedPoints + 1);\n    m_speedPoints++;\n\n    auto workspaceIDLeft  = getWorkspaceIDNameFromString((*PSWIPEUSER ? \"r-1\" : \"m-1\")).id;\n    auto workspaceIDRight = getWorkspaceIDNameFromString((*PSWIPEUSER ? \"r+1\" : \"m+1\")).id;\n\n    if ((workspaceIDLeft == WORKSPACE_INVALID || workspaceIDRight == WORKSPACE_INVALID || workspaceIDLeft == m_workspaceBegin->m_id) && !*PSWIPENEW) {\n        m_workspaceBegin = nullptr; // invalidate the swipe\n        return;\n    }\n\n    m_workspaceBegin->m_forceRendering = true;\n\n    m_delta = std::clamp(m_delta, sc<double>(-SWIPEDISTANCE), sc<double>(SWIPEDISTANCE));\n\n    if ((m_workspaceBegin->m_id == workspaceIDLeft && *PSWIPENEW && (m_delta < 0)) ||\n        (m_delta > 0 && m_workspaceBegin->getWindows() == 0 && workspaceIDRight <= m_workspaceBegin->m_id) || (m_delta < 0 && m_workspaceBegin->m_id <= workspaceIDLeft)) {\n\n        m_delta = 0;\n        g_pHyprRenderer->damageMonitor(m_monitor.lock());\n        m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(0.0, 0.0));\n        return;\n    }\n\n    if (*PSWIPEDIRLOCK) {\n        if (m_initialDirection != 0 && m_initialDirection != (m_delta < 0 ? -1 : 1))\n            m_delta = 0;\n        else if (m_initialDirection == 0 && abs(m_delta) > *PSWIPEDIRLOCKTHRESHOLD)\n            m_initialDirection = m_delta < 0 ? -1 : 1;\n    }\n\n    if (m_delta < 0) {\n        const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceIDLeft);\n\n        if (workspaceIDLeft > m_workspaceBegin->m_id || !PWORKSPACE) {\n            if (*PSWIPENEW) {\n                g_pHyprRenderer->damageMonitor(m_monitor.lock());\n\n                if (VERTANIMS)\n                    m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(0.0, ((-m_delta) / SWIPEDISTANCE) * YDISTANCE));\n                else\n                    m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(((-m_delta) / SWIPEDISTANCE) * XDISTANCE, 0.0));\n\n                m_workspaceBegin->updateWindowDecos();\n                return;\n            }\n            m_delta = 0;\n            return;\n        }\n\n        PWORKSPACE->m_forceRendering = true;\n        PWORKSPACE->m_alpha->setValueAndWarp(1.f);\n\n        if (workspaceIDLeft != workspaceIDRight && workspaceIDRight != m_workspaceBegin->m_id) {\n            const auto PWORKSPACER = g_pCompositor->getWorkspaceByID(workspaceIDRight);\n\n            if (PWORKSPACER) {\n                PWORKSPACER->m_forceRendering = false;\n                PWORKSPACER->m_alpha->setValueAndWarp(0.f);\n            }\n        }\n\n        if (VERTANIMS) {\n            PWORKSPACE->m_renderOffset->setValueAndWarp(Vector2D(0.0, ((-m_delta) / SWIPEDISTANCE) * YDISTANCE - YDISTANCE));\n            m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(0.0, ((-m_delta) / SWIPEDISTANCE) * YDISTANCE));\n        } else {\n            PWORKSPACE->m_renderOffset->setValueAndWarp(Vector2D(((-m_delta) / SWIPEDISTANCE) * XDISTANCE - XDISTANCE, 0.0));\n            m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(((-m_delta) / SWIPEDISTANCE) * XDISTANCE, 0.0));\n        }\n\n        PWORKSPACE->updateWindowDecos();\n    } else {\n        const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceIDRight);\n\n        if (workspaceIDRight < m_workspaceBegin->m_id || !PWORKSPACE) {\n            if (*PSWIPENEW) {\n                g_pHyprRenderer->damageMonitor(m_monitor.lock());\n\n                if (VERTANIMS)\n                    m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(0.0, ((-m_delta) / SWIPEDISTANCE) * YDISTANCE));\n                else\n                    m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(((-m_delta) / SWIPEDISTANCE) * XDISTANCE, 0.0));\n\n                m_workspaceBegin->updateWindowDecos();\n                return;\n            }\n            m_delta = 0;\n            return;\n        }\n\n        PWORKSPACE->m_forceRendering = true;\n        PWORKSPACE->m_alpha->setValueAndWarp(1.f);\n\n        if (workspaceIDLeft != workspaceIDRight && workspaceIDLeft != m_workspaceBegin->m_id) {\n            const auto PWORKSPACEL = g_pCompositor->getWorkspaceByID(workspaceIDLeft);\n\n            if (PWORKSPACEL) {\n                PWORKSPACEL->m_forceRendering = false;\n                PWORKSPACEL->m_alpha->setValueAndWarp(0.f);\n            }\n        }\n\n        if (VERTANIMS) {\n            PWORKSPACE->m_renderOffset->setValueAndWarp(Vector2D(0.0, ((-m_delta) / SWIPEDISTANCE) * YDISTANCE + YDISTANCE));\n            m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(0.0, ((-m_delta) / SWIPEDISTANCE) * YDISTANCE));\n        } else {\n            PWORKSPACE->m_renderOffset->setValueAndWarp(Vector2D(((-m_delta) / SWIPEDISTANCE) * XDISTANCE + XDISTANCE, 0.0));\n            m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(((-m_delta) / SWIPEDISTANCE) * XDISTANCE, 0.0));\n        }\n\n        PWORKSPACE->updateWindowDecos();\n    }\n\n    g_pHyprRenderer->damageMonitor(m_monitor.lock());\n\n    m_workspaceBegin->updateWindowDecos();\n\n    if (*PSWIPEFOREVER) {\n        if (abs(m_delta) >= SWIPEDISTANCE) {\n            end();\n            begin();\n        }\n    }\n}\n\nvoid CUnifiedWorkspaceSwipeGesture::end() {\n    if (!isGestureInProgress())\n        return;\n\n    static auto PSWIPEPERC    = CConfigValue<Hyprlang::FLOAT>(\"gestures:workspace_swipe_cancel_ratio\");\n    static auto PSWIPEDIST    = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_distance\");\n    static auto PSWIPEFORC    = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_min_speed_to_force\");\n    static auto PSWIPENEW     = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_create_new\");\n    static auto PSWIPEUSER    = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_use_r\");\n    static auto PWORKSPACEGAP = CConfigValue<Hyprlang::INT>(\"general:gaps_workspaces\");\n    const auto  ANIMSTYLE     = m_workspaceBegin->m_renderOffset->getStyle();\n    const bool  VERTANIMS     = ANIMSTYLE == \"slidevert\" || ANIMSTYLE.starts_with(\"slidefadevert\");\n\n    // commit\n    auto       workspaceIDLeft  = getWorkspaceIDNameFromString((*PSWIPEUSER ? \"r-1\" : \"m-1\")).id;\n    auto       workspaceIDRight = getWorkspaceIDNameFromString((*PSWIPEUSER ? \"r+1\" : \"m+1\")).id;\n    const auto SWIPEDISTANCE    = std::clamp(*PSWIPEDIST, sc<int64_t>(1LL), sc<int64_t>(UINT32_MAX));\n\n    // If we've been swiping off the right end with PSWIPENEW enabled, there is\n    // no workspace there yet, and we need to choose an ID for a new one now.\n    if (workspaceIDRight <= m_workspaceBegin->m_id && *PSWIPENEW)\n        workspaceIDRight = getWorkspaceIDNameFromString(\"r+1\").id;\n\n    auto         PWORKSPACER = g_pCompositor->getWorkspaceByID(workspaceIDRight); // not guaranteed if PSWIPENEW || PSWIPENUMBER\n    auto         PWORKSPACEL = g_pCompositor->getWorkspaceByID(workspaceIDLeft);  // not guaranteed if PSWIPENUMBER\n\n    const auto   RENDEROFFSETMIDDLE = m_workspaceBegin->m_renderOffset->value();\n    const auto   XDISTANCE          = m_monitor->m_size.x + *PWORKSPACEGAP;\n    const auto   YDISTANCE          = m_monitor->m_size.y + *PWORKSPACEGAP;\n\n    PHLWORKSPACE pSwitchedTo = nullptr;\n\n    if ((abs(m_delta) < SWIPEDISTANCE * *PSWIPEPERC && (*PSWIPEFORC == 0 || (*PSWIPEFORC != 0 && m_avgSpeed < *PSWIPEFORC))) || abs(m_delta) < 2) {\n        // revert\n        if (abs(m_delta) < 2) {\n            if (PWORKSPACEL)\n                PWORKSPACEL->m_renderOffset->setValueAndWarp(Vector2D(0, 0));\n            if (PWORKSPACER)\n                PWORKSPACER->m_renderOffset->setValueAndWarp(Vector2D(0, 0));\n            m_workspaceBegin->m_renderOffset->setValueAndWarp(Vector2D(0, 0));\n        } else {\n            if (m_delta < 0) {\n                // to left\n\n                if (PWORKSPACEL) {\n                    if (VERTANIMS)\n                        *PWORKSPACEL->m_renderOffset = Vector2D{0.0, -YDISTANCE};\n                    else\n                        *PWORKSPACEL->m_renderOffset = Vector2D{-XDISTANCE, 0.0};\n                }\n            } else if (PWORKSPACER) {\n                // to right\n                if (VERTANIMS)\n                    *PWORKSPACER->m_renderOffset = Vector2D{0.0, YDISTANCE};\n                else\n                    *PWORKSPACER->m_renderOffset = Vector2D{XDISTANCE, 0.0};\n            }\n\n            *m_workspaceBegin->m_renderOffset = Vector2D();\n        }\n\n        pSwitchedTo = m_workspaceBegin;\n    } else if (m_delta < 0) {\n        // switch to left\n        const auto RENDEROFFSET = PWORKSPACEL ? PWORKSPACEL->m_renderOffset->value() : Vector2D();\n\n        if (PWORKSPACEL)\n            m_monitor->changeWorkspace(workspaceIDLeft);\n        else {\n            m_monitor->changeWorkspace(g_pCompositor->createNewWorkspace(workspaceIDLeft, m_monitor->m_id));\n            PWORKSPACEL = g_pCompositor->getWorkspaceByID(workspaceIDLeft);\n        }\n\n        PWORKSPACEL->m_renderOffset->setValue(RENDEROFFSET);\n        PWORKSPACEL->m_alpha->setValueAndWarp(1.f);\n\n        m_workspaceBegin->m_renderOffset->setValue(RENDEROFFSETMIDDLE);\n        if (VERTANIMS)\n            *m_workspaceBegin->m_renderOffset = Vector2D(0.0, YDISTANCE);\n        else\n            *m_workspaceBegin->m_renderOffset = Vector2D(XDISTANCE, 0.0);\n        m_workspaceBegin->m_alpha->setValueAndWarp(1.f);\n\n        g_pInputManager->unconstrainMouse();\n\n        Log::logger->log(Log::DEBUG, \"Ended swipe to the left\");\n\n        pSwitchedTo = PWORKSPACEL;\n    } else {\n        // switch to right\n        const auto RENDEROFFSET = PWORKSPACER ? PWORKSPACER->m_renderOffset->value() : Vector2D();\n\n        if (PWORKSPACER)\n            m_monitor->changeWorkspace(workspaceIDRight);\n        else {\n            m_monitor->changeWorkspace(g_pCompositor->createNewWorkspace(workspaceIDRight, m_monitor->m_id));\n            PWORKSPACER = g_pCompositor->getWorkspaceByID(workspaceIDRight);\n        }\n\n        PWORKSPACER->m_renderOffset->setValue(RENDEROFFSET);\n        PWORKSPACER->m_alpha->setValueAndWarp(1.f);\n\n        m_workspaceBegin->m_renderOffset->setValue(RENDEROFFSETMIDDLE);\n        if (VERTANIMS)\n            *m_workspaceBegin->m_renderOffset = Vector2D(0.0, -YDISTANCE);\n        else\n            *m_workspaceBegin->m_renderOffset = Vector2D(-XDISTANCE, 0.0);\n        m_workspaceBegin->m_alpha->setValueAndWarp(1.f);\n\n        g_pInputManager->unconstrainMouse();\n\n        Log::logger->log(Log::DEBUG, \"Ended swipe to the right\");\n\n        pSwitchedTo = PWORKSPACER;\n    }\n\n    g_pHyprRenderer->damageMonitor(m_monitor.lock());\n\n    if (PWORKSPACEL)\n        PWORKSPACEL->m_forceRendering = false;\n    if (PWORKSPACER)\n        PWORKSPACER->m_forceRendering = false;\n    m_workspaceBegin->m_forceRendering = false;\n\n    m_workspaceBegin   = nullptr;\n    m_initialDirection = 0;\n\n    g_pInputManager->refocus();\n\n    // apply alpha\n    for (auto const& ls : Desktop::focusState()->monitor()->m_layerSurfaceLayers[2]) {\n        *ls->m_alpha = pSwitchedTo->m_hasFullscreenWindow && pSwitchedTo->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f;\n    }\n}\n"
  },
  {
    "path": "src/managers/input/UnifiedWorkspaceSwipeGesture.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../desktop/DesktopTypes.hpp\"\n\nclass CUnifiedWorkspaceSwipeGesture {\n  public:\n    void begin();\n    void update(double delta);\n    void end();\n\n    bool isGestureInProgress();\n\n  private:\n    PHLWORKSPACE  m_workspaceBegin = nullptr;\n    PHLMONITORREF m_monitor;\n\n    double        m_delta            = 0;\n    int           m_initialDirection = 0;\n    float         m_avgSpeed         = 0;\n    int           m_speedPoints      = 0;\n    int           m_touchID          = 0;\n\n    friend class CWorkspaceSwipeGesture;\n    friend class CInputManager;\n};\n\ninline UP<CUnifiedWorkspaceSwipeGesture> g_pUnifiedWorkspaceSwipe = makeUnique<CUnifiedWorkspaceSwipeGesture>();\n"
  },
  {
    "path": "src/managers/input/trackpad/GestureTypes.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n\nenum eTrackpadGestureDirection : uint8_t {\n    TRACKPAD_GESTURE_DIR_NONE = 0,\n    TRACKPAD_GESTURE_DIR_SWIPE,\n    TRACKPAD_GESTURE_DIR_LEFT,\n    TRACKPAD_GESTURE_DIR_RIGHT,\n    TRACKPAD_GESTURE_DIR_UP,\n    TRACKPAD_GESTURE_DIR_DOWN,\n    TRACKPAD_GESTURE_DIR_VERTICAL,\n    TRACKPAD_GESTURE_DIR_HORIZONTAL,\n    TRACKPAD_GESTURE_DIR_PINCH,\n    TRACKPAD_GESTURE_DIR_PINCH_OUT,\n    TRACKPAD_GESTURE_DIR_PINCH_IN,\n};"
  },
  {
    "path": "src/managers/input/trackpad/TrackpadGestures.cpp",
    "content": "#include \"TrackpadGestures.hpp\"\n\n#include \"../InputManager.hpp\"\n#include \"../../../config/ConfigValue.hpp\"\n#include \"../../../protocols/ShortcutsInhibit.hpp\"\n\n#include <ranges>\n\nvoid CTrackpadGestures::clearGestures() {\n    m_gestures.clear();\n}\n\neTrackpadGestureDirection CTrackpadGestures::dirForString(const std::string_view& s) {\n    std::string lc = std::string{s};\n    std::ranges::transform(lc, lc.begin(), ::tolower);\n\n    if (lc == \"swipe\")\n        return TRACKPAD_GESTURE_DIR_SWIPE;\n    if (lc == \"left\" || lc == \"l\")\n        return TRACKPAD_GESTURE_DIR_LEFT;\n    if (lc == \"right\" || lc == \"r\")\n        return TRACKPAD_GESTURE_DIR_RIGHT;\n    if (lc == \"up\" || lc == \"u\" || lc == \"top\" || lc == \"t\")\n        return TRACKPAD_GESTURE_DIR_UP;\n    if (lc == \"down\" || lc == \"d\" || lc == \"bottom\" || lc == \"b\")\n        return TRACKPAD_GESTURE_DIR_DOWN;\n    if (lc == \"horizontal\" || lc == \"horiz\")\n        return TRACKPAD_GESTURE_DIR_HORIZONTAL;\n    if (lc == \"vertical\" || lc == \"vert\")\n        return TRACKPAD_GESTURE_DIR_VERTICAL;\n    if (lc == \"pinch\")\n        return TRACKPAD_GESTURE_DIR_PINCH;\n    if (lc == \"pinchin\" || lc == \"zoomin\")\n        return TRACKPAD_GESTURE_DIR_PINCH_IN;\n    if (lc == \"pinchout\" || lc == \"zoomout\")\n        return TRACKPAD_GESTURE_DIR_PINCH_OUT;\n\n    return TRACKPAD_GESTURE_DIR_NONE;\n}\n\nconst char* CTrackpadGestures::stringForDir(eTrackpadGestureDirection dir) {\n    switch (dir) {\n        case TRACKPAD_GESTURE_DIR_HORIZONTAL: return \"HORIZONTAL\";\n        case TRACKPAD_GESTURE_DIR_VERTICAL: return \"VERTICAL\";\n        case TRACKPAD_GESTURE_DIR_LEFT: return \"LEFT\";\n        case TRACKPAD_GESTURE_DIR_RIGHT: return \"RIGHT\";\n        case TRACKPAD_GESTURE_DIR_UP: return \"UP\";\n        case TRACKPAD_GESTURE_DIR_DOWN: return \"DOWN\";\n        case TRACKPAD_GESTURE_DIR_SWIPE: return \"SWIPE\";\n        case TRACKPAD_GESTURE_DIR_PINCH: return \"PINCH\";\n        case TRACKPAD_GESTURE_DIR_PINCH_IN: return \"PINCH_IN\";\n        case TRACKPAD_GESTURE_DIR_PINCH_OUT: return \"PINCH_OUT\";\n        default: return \"ERROR\";\n    }\n    return \"ERROR\";\n}\n\nstd::expected<void, std::string> CTrackpadGestures::addGesture(UP<ITrackpadGesture>&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask,\n                                                               float deltaScale, bool disableInhibit) {\n    for (const auto& g : m_gestures) {\n        if (g->fingerCount != fingerCount)\n            continue;\n\n        if (g->modMask != modMask)\n            continue;\n\n        eTrackpadGestureDirection axis = TRACKPAD_GESTURE_DIR_NONE;\n        switch (direction) {\n            case TRACKPAD_GESTURE_DIR_UP:\n            case TRACKPAD_GESTURE_DIR_DOWN:\n            case TRACKPAD_GESTURE_DIR_VERTICAL: axis = TRACKPAD_GESTURE_DIR_VERTICAL; break;\n            case TRACKPAD_GESTURE_DIR_LEFT:\n            case TRACKPAD_GESTURE_DIR_RIGHT:\n            case TRACKPAD_GESTURE_DIR_HORIZONTAL: axis = TRACKPAD_GESTURE_DIR_HORIZONTAL; break;\n            case TRACKPAD_GESTURE_DIR_SWIPE: axis = TRACKPAD_GESTURE_DIR_SWIPE; break;\n            case TRACKPAD_GESTURE_DIR_PINCH:\n            case TRACKPAD_GESTURE_DIR_PINCH_IN:\n            case TRACKPAD_GESTURE_DIR_PINCH_OUT: axis = TRACKPAD_GESTURE_DIR_PINCH; break;\n            default: TRACKPAD_GESTURE_DIR_NONE; break;\n        }\n\n        if (g->direction == axis || g->direction == direction ||\n            ((axis == TRACKPAD_GESTURE_DIR_VERTICAL || axis == TRACKPAD_GESTURE_DIR_HORIZONTAL) && g->direction == TRACKPAD_GESTURE_DIR_SWIPE)) {\n            return std::unexpected(\n                std::format(\"Gesture will be overshadowed by a previous gesture. Previous {} shadows new {}\", stringForDir(g->direction), stringForDir(direction)));\n        }\n    }\n\n    m_gestures.emplace_back(makeShared<CTrackpadGestures::SGestureData>(std::move(gesture), fingerCount, modMask, direction, deltaScale, disableInhibit));\n\n    return {};\n}\n\nstd::expected<void, std::string> CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale,\n                                                                  bool disableInhibit) {\n    const auto IT = std::ranges::find_if(m_gestures, [&](const auto& g) {\n        return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale && g->disableInhibit == disableInhibit;\n    });\n\n    if (IT == m_gestures.end())\n        return std::unexpected(\"Can't remove a non-existent gesture\");\n\n    std::erase(m_gestures, *IT);\n\n    return {};\n}\n\nvoid CTrackpadGestures::gestureBegin(const IPointer::SSwipeBeginEvent& e) {\n    if (m_activeGesture) {\n        Log::logger->log(Log::ERR, \"CTrackpadGestures::gestureBegin (swipe) but m_activeGesture is already present\");\n        return;\n    }\n\n    m_gestureFindFailed = false;\n    m_currentTotalDelta = {};\n\n    // nothing here. We need to wait for the first update to determine the delta.\n}\n\nvoid CTrackpadGestures::gestureUpdate(const IPointer::SSwipeUpdateEvent& e) {\n    static auto PDISABLEINHIBIT = CConfigValue<Hyprlang::INT>(\"binds:disable_keybind_grabbing\");\n\n    if (m_gestureFindFailed)\n        return;\n\n    m_currentTotalDelta += e.delta;\n\n    // 5 was chosen because I felt like that's a good number.\n    if (!m_activeGesture && (std::abs(m_currentTotalDelta.x) < 5 && std::abs(m_currentTotalDelta.y) < 5)) {\n        Log::logger->log(Log::TRACE, \"CTrackpadGestures::gestureUpdate (swipe): gesture delta too small to start considering, waiting\");\n        return;\n    }\n\n    if (!m_activeGesture) {\n        // try to find a gesture that matches our current state\n\n        auto direction = TRACKPAD_GESTURE_DIR_NONE;\n        auto axis      = std::abs(m_currentTotalDelta.x) > std::abs(m_currentTotalDelta.y) ? TRACKPAD_GESTURE_DIR_HORIZONTAL : TRACKPAD_GESTURE_DIR_VERTICAL;\n\n        if (axis == TRACKPAD_GESTURE_DIR_HORIZONTAL)\n            direction = m_currentTotalDelta.x < 0 ? TRACKPAD_GESTURE_DIR_LEFT : TRACKPAD_GESTURE_DIR_RIGHT;\n        else\n            direction = m_currentTotalDelta.y < 0 ? TRACKPAD_GESTURE_DIR_UP : TRACKPAD_GESTURE_DIR_DOWN;\n\n        const auto MODS = g_pInputManager->getModsFromAllKBs();\n\n        for (const auto& g : m_gestures) {\n            if (g->direction != axis && g->direction != direction && g->direction != TRACKPAD_GESTURE_DIR_SWIPE)\n                continue;\n\n            if (g->fingerCount != e.fingers)\n                continue;\n\n            if (g->modMask != MODS)\n                continue;\n\n            if (PROTO::shortcutsInhibit->isInhibited() && !*PDISABLEINHIBIT && !g->disableInhibit)\n                continue;\n\n            m_activeGesture     = g;\n            g->currentDirection = g->gesture->isDirectionSensitive() ? g->direction : direction;\n            m_activeGesture->gesture->begin({.swipe = &e, .direction = direction, .scale = g->deltaScale});\n            break;\n        }\n\n        if (!m_activeGesture) {\n            m_gestureFindFailed = true;\n            return;\n        }\n    }\n\n    m_activeGesture->gesture->update({.swipe = &e, .direction = m_activeGesture->currentDirection, .scale = m_activeGesture->deltaScale});\n}\n\nvoid CTrackpadGestures::gestureEnd(const IPointer::SSwipeEndEvent& e) {\n    if (!m_activeGesture)\n        return;\n\n    m_activeGesture->gesture->end({.swipe = &e, .direction = m_activeGesture->direction, .scale = m_activeGesture->deltaScale});\n\n    m_activeGesture.reset();\n}\n\nvoid CTrackpadGestures::gestureBegin(const IPointer::SPinchBeginEvent& e) {\n    if (m_activeGesture) {\n        Log::logger->log(Log::ERR, \"CTrackpadGestures::gestureBegin (pinch) but m_activeGesture is already present\");\n        return;\n    }\n\n    m_gestureFindFailed = false;\n\n    // nothing here. We need to wait for the first update to determine the delta.\n}\n\nvoid CTrackpadGestures::gestureUpdate(const IPointer::SPinchUpdateEvent& e) {\n    static auto PDISABLEINHIBIT = CConfigValue<Hyprlang::INT>(\"binds:disable_keybind_grabbing\");\n\n    if (m_gestureFindFailed)\n        return;\n\n    // 0.1 was chosen because I felt like that's a good number.\n    if (!m_activeGesture && std::abs(e.scale - 1.F) < 0.1) {\n        Log::logger->log(Log::TRACE, \"CTrackpadGestures::gestureUpdate (pinch): gesture delta too small to start considering, waiting\");\n        return;\n    }\n\n    if (!m_activeGesture) {\n        // try to find a gesture that matches our current state\n\n        auto       direction = e.scale < 1.F ? TRACKPAD_GESTURE_DIR_PINCH_OUT : TRACKPAD_GESTURE_DIR_PINCH_IN;\n        auto       axis      = TRACKPAD_GESTURE_DIR_PINCH;\n\n        const auto MODS = g_pInputManager->getModsFromAllKBs();\n\n        for (const auto& g : m_gestures) {\n            if (g->direction != axis && g->direction != direction)\n                continue;\n\n            if (g->fingerCount != e.fingers)\n                continue;\n\n            if (g->modMask != MODS)\n                continue;\n\n            if (PROTO::shortcutsInhibit->isInhibited() && !*PDISABLEINHIBIT && !g->disableInhibit)\n                continue;\n\n            m_activeGesture     = g;\n            g->currentDirection = g->gesture->isDirectionSensitive() ? g->direction : direction;\n            m_activeGesture->gesture->begin({.pinch = &e, .direction = direction});\n            break;\n        }\n\n        if (!m_activeGesture) {\n            m_gestureFindFailed = true;\n            return;\n        }\n    }\n\n    m_activeGesture->gesture->update({.pinch = &e, .direction = m_activeGesture->currentDirection});\n}\n\nvoid CTrackpadGestures::gestureEnd(const IPointer::SPinchEndEvent& e) {\n    if (!m_activeGesture)\n        return;\n\n    m_activeGesture->gesture->end({.pinch = &e, .direction = m_activeGesture->direction});\n\n    m_activeGesture.reset();\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/TrackpadGestures.hpp",
    "content": "#pragma once\n\n#include \"../../../devices/IPointer.hpp\"\n\n#include \"gestures/ITrackpadGesture.hpp\"\n#include \"GestureTypes.hpp\"\n\n#include <vector>\n#include <expected>\n\nclass CTrackpadGestures {\n  public:\n    void                             clearGestures();\n    std::expected<void, std::string> addGesture(UP<ITrackpadGesture>&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale,\n                                                bool disableInhibit);\n    std::expected<void, std::string> removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, bool disableInhibit);\n\n    void                             gestureBegin(const IPointer::SSwipeBeginEvent& e);\n    void                             gestureUpdate(const IPointer::SSwipeUpdateEvent& e);\n    void                             gestureEnd(const IPointer::SSwipeEndEvent& e);\n\n    void                             gestureBegin(const IPointer::SPinchBeginEvent& e);\n    void                             gestureUpdate(const IPointer::SPinchUpdateEvent& e);\n    void                             gestureEnd(const IPointer::SPinchEndEvent& e);\n\n    eTrackpadGestureDirection        dirForString(const std::string_view& s);\n    const char*                      stringForDir(eTrackpadGestureDirection dir);\n\n  private:\n    struct SGestureData {\n        UP<ITrackpadGesture>      gesture;\n        size_t                    fingerCount      = 0;\n        uint32_t                  modMask          = 0;\n        eTrackpadGestureDirection direction        = TRACKPAD_GESTURE_DIR_NONE; // configured dir\n        float                     deltaScale       = 1.F;\n        bool                      disableInhibit   = false;\n        eTrackpadGestureDirection currentDirection = TRACKPAD_GESTURE_DIR_NONE; // actual dir of that select swipe\n    };\n\n    std::vector<SP<SGestureData>> m_gestures;\n\n    Vector2D                      m_currentTotalDelta = {};\n    SP<SGestureData>              m_activeGesture     = nullptr;\n    bool                          m_gestureFindFailed = false;\n};\n\ninline UP<CTrackpadGestures> g_pTrackpadGestures = makeUnique<CTrackpadGestures>();\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/CloseGesture.cpp",
    "content": "#include \"CloseGesture.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../managers/animation/DesktopAnimationManager.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n#include \"../../../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../../../managers/eventLoop/EventLoopTimer.hpp\"\n#include \"../../../../config/ConfigValue.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../layout/target/Target.hpp\"\n\nconstexpr const float                   MAX_DISTANCE = 200.F;\n\nstatic std::vector<SP<CEventLoopTimer>> trackpadCloseTimers;\n\n//\nstatic Vector2D lerpVal(const Vector2D& from, const Vector2D& to, const float& t) {\n    return Vector2D{\n        from.x + ((to.x - from.x) * t),\n        from.y + ((to.y - from.y) * t),\n    };\n}\n\nstatic float lerpVal(const float& from, const float& to, const float& t) {\n    return from + ((to - from) * t);\n}\n\nvoid CCloseTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    m_window = Desktop::focusState()->window();\n\n    if (!m_window)\n        return;\n\n    m_alphaFrom = m_window->m_alpha->goal();\n    m_posFrom   = m_window->m_realPosition->goal();\n    m_sizeFrom  = m_window->m_realSize->goal();\n\n    g_pDesktopAnimationManager->startAnimation(m_window.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT, true);\n    *m_window->m_alpha = 0.f;\n\n    m_alphaTo = m_window->m_alpha->goal();\n    m_posTo   = m_window->m_realPosition->goal();\n    m_sizeTo  = m_window->m_realSize->goal();\n\n    m_window->m_alpha->setValueAndWarp(m_alphaFrom);\n    m_window->m_realPosition->setValueAndWarp(m_posFrom);\n    m_window->m_realSize->setValueAndWarp(m_sizeFrom);\n\n    m_lastDelta = 0.F;\n}\n\nvoid CCloseTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!m_window)\n        return;\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n\n    m_lastDelta += distance(e);\n\n    const auto FADEPERCENT = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    m_window->m_alpha->setValueAndWarp(lerpVal(m_alphaFrom, m_alphaTo, FADEPERCENT));\n    m_window->m_realPosition->setValueAndWarp(lerpVal(m_posFrom, m_posTo, FADEPERCENT));\n    m_window->m_realSize->setValueAndWarp(lerpVal(m_sizeFrom, m_sizeTo, FADEPERCENT));\n\n    g_pDecorationPositioner->onWindowUpdate(m_window.lock());\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n}\n\nvoid CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    static const auto PTIMEOUT = CConfigValue<Hyprlang::INT>(\"gestures:close_max_timeout\");\n\n    if (!m_window)\n        return;\n\n    const auto COMPLETION = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    if (COMPLETION < 0.2F) {\n        // revert the animation\n        g_pHyprRenderer->damageWindow(m_window.lock());\n        *m_window->m_alpha        = m_alphaFrom;\n        *m_window->m_realPosition = m_posFrom;\n        *m_window->m_realSize     = m_sizeFrom;\n        return;\n    }\n\n    // commence. Close the window and restore our current state to avoid a harsh anim\n    const auto CURRENT_ALPHA = m_window->m_alpha->value();\n    const auto CURRENT_POS   = m_window->m_realPosition->value();\n    const auto CURRENT_SIZE  = m_window->m_realSize->value();\n\n    g_pCompositor->closeWindow(m_window.lock());\n\n    m_window->m_alpha->setValueAndWarp(CURRENT_ALPHA);\n    m_window->m_realPosition->setValueAndWarp(CURRENT_POS);\n    m_window->m_realSize->setValueAndWarp(CURRENT_SIZE);\n\n    // this is a kinda hack, but oh well.\n    m_window->m_realPosition->setCallbackOnBegin(\n        [CURRENT_POS, window = m_window](auto) {\n            if (!window || !window->m_isMapped)\n                return;\n\n            window->m_realPosition->setValueAndWarp(CURRENT_POS);\n        },\n        false);\n\n    m_window->m_realSize->setCallbackOnBegin(\n        [CURRENT_SIZE, window = m_window](auto) {\n            if (!window || !window->m_isMapped)\n                return;\n\n            window->m_realSize->setValueAndWarp(CURRENT_SIZE);\n        },\n        false);\n\n    // we give windows 2s to close. If they don't, pop them back in.\n    auto timer = makeShared<CEventLoopTimer>(\n        std::chrono::milliseconds(*PTIMEOUT),\n        [window = m_window](SP<CEventLoopTimer> self, void* data) {\n            std::erase(trackpadCloseTimers, self);\n\n            // if after 2 seconds the window is still alive and mapped, we revert our changes.\n            if (!window)\n                return;\n\n            window->m_realPosition->setCallbackOnBegin(nullptr);\n            window->m_realSize->setCallbackOnBegin(nullptr);\n\n            if (!window->m_isMapped)\n                return;\n\n            window->layoutTarget()->recalc();\n            window->updateDecorationValues();\n            window->sendWindowSize(true);\n            *window->m_alpha = 1.F;\n        },\n        nullptr);\n    trackpadCloseTimers.emplace_back(timer);\n    g_pEventLoopManager->addTimer(timer);\n\n    m_window.reset();\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/CloseGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\n#include \"../../../../desktop/DesktopTypes.hpp\"\n\nclass CCloseTrackpadGesture : public ITrackpadGesture {\n  public:\n    CCloseTrackpadGesture()          = default;\n    virtual ~CCloseTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    PHLWINDOWREF m_window;\n\n    Vector2D     m_posFrom, m_posTo, m_sizeFrom, m_sizeTo;\n    float        m_alphaFrom = 0.F, m_alphaTo = 0.F;\n\n    float        m_lastDelta = 0.F;\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/CursorZoomGesture.cpp",
    "content": "#include \"CursorZoomGesture.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../helpers/Monitor.hpp\"\n\nCCursorZoomTrackpadGesture::CCursorZoomTrackpadGesture(const std::string& first, const std::string& second) {\n    try {\n        m_zoomValue = std::stof(first);\n    } catch (...) { ; }\n\n    if (second == \"mult\")\n        m_mode = MODE_MULT;\n}\n\nvoid CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    if (m_mode == MODE_TOGGLE)\n        m_zoomed = !m_zoomed;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        switch (m_mode) {\n            case MODE_TOGGLE:\n                static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>(\"cursor:zoom_factor\");\n                *m->m_cursorZoom        = m_zoomed ? m_zoomValue : *PZOOMFACTOR;\n                break;\n            case MODE_MULT: *m->m_cursorZoom = std::clamp(m->m_cursorZoom->goal() * m_zoomValue, 1.0F, 100.0F); break;\n        }\n    }\n}\n\nvoid CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {}\nvoid CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/CursorZoomGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\nclass CCursorZoomTrackpadGesture : public ITrackpadGesture {\n  public:\n    CCursorZoomTrackpadGesture(const std::string& zoomLevel, const std::string& mode);\n    virtual ~CCursorZoomTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    float              m_zoomValue = 1.0;\n    inline static bool m_zoomed    = false;\n\n    enum eMode : uint8_t {\n        MODE_TOGGLE = 0,\n        MODE_MULT,\n    };\n\n    eMode m_mode = MODE_TOGGLE;\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/DispatcherGesture.cpp",
    "content": "#include \"DispatcherGesture.hpp\"\n\n#include \"../../../../managers/KeybindManager.hpp\"\n\nCDispatcherTrackpadGesture::CDispatcherTrackpadGesture(const std::string& dispatcher, const std::string& data) : m_dispatcher(dispatcher), m_data(data) {\n    ;\n}\n\nvoid CDispatcherTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ; // intentionally blank\n}\n\nvoid CDispatcherTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    ; // intentionally blank\n}\n\nvoid CDispatcherTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    if (!g_pKeybindManager->m_dispatchers.contains(m_dispatcher))\n        return;\n\n    g_pKeybindManager->m_dispatchers.at(m_dispatcher)(m_data);\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/DispatcherGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\nclass CDispatcherTrackpadGesture : public ITrackpadGesture {\n  public:\n    CDispatcherTrackpadGesture(const std::string& dispatcher, const std::string& data);\n    virtual ~CDispatcherTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    std::string m_dispatcher, m_data;\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/FloatGesture.cpp",
    "content": "#include \"FloatGesture.hpp\"\n\n#include \"../../../../render/Renderer.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../desktop/view/Window.hpp\"\n#include \"../../../../layout/LayoutManager.hpp\"\n#include \"../../../../layout/target/WindowTarget.hpp\"\n\nconstexpr const float MAX_DISTANCE = 250.F;\n\n//\nstatic Vector2D lerpVal(const Vector2D& from, const Vector2D& to, const float& t) {\n    return Vector2D{\n        from.x + ((to.x - from.x) * t),\n        from.y + ((to.y - from.y) * t),\n    };\n}\n\nCFloatTrackpadGesture::CFloatTrackpadGesture(const std::string_view& data) {\n    std::string lc = std::string{data};\n    std::ranges::transform(lc, lc.begin(), ::tolower);\n\n    if (lc.starts_with(\"float\"))\n        m_mode = FLOAT_MODE_FLOAT;\n    else if (lc.starts_with(\"tile\"))\n        m_mode = FLOAT_MODE_TILE;\n    else\n        m_mode = FLOAT_MODE_TOGGLE;\n}\n\nvoid CFloatTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    m_window = Desktop::focusState()->window();\n\n    if (!m_window)\n        return;\n\n    if ((m_window->m_isFloating && m_mode == FLOAT_MODE_FLOAT) || (!m_window->m_isFloating && m_mode == FLOAT_MODE_TILE)) {\n        m_window.reset();\n        return;\n    }\n\n    g_layoutManager->changeFloatingMode(m_window->layoutTarget());\n\n    m_posFrom  = m_window->m_realPosition->begun();\n    m_sizeFrom = m_window->m_realSize->begun();\n\n    m_posTo  = m_window->m_realPosition->goal();\n    m_sizeTo = m_window->m_realSize->goal();\n\n    m_lastDelta = 0.F;\n}\n\nvoid CFloatTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!m_window)\n        return;\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n\n    m_lastDelta += distance(e);\n\n    const auto FADEPERCENT = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    m_window->m_realPosition->setValueAndWarp(lerpVal(m_posFrom, m_posTo, FADEPERCENT));\n    m_window->m_realSize->setValueAndWarp(lerpVal(m_sizeFrom, m_sizeTo, FADEPERCENT));\n\n    g_pDecorationPositioner->onWindowUpdate(m_window.lock());\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n}\n\nvoid CFloatTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    if (!m_window)\n        return;\n\n    const auto COMPLETION = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    if (COMPLETION < 0.2F) {\n        // revert the animation\n        g_pHyprRenderer->damageWindow(m_window.lock());\n        g_layoutManager->changeFloatingMode(m_window->layoutTarget());\n        return;\n    }\n\n    *m_window->m_realPosition = m_posTo;\n    *m_window->m_realSize     = m_sizeTo;\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/FloatGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\n#include \"../../../../desktop/DesktopTypes.hpp\"\n\nclass CFloatTrackpadGesture : public ITrackpadGesture {\n  public:\n    CFloatTrackpadGesture(const std::string_view& mode);\n    virtual ~CFloatTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    PHLWINDOWREF m_window;\n\n    Vector2D     m_posFrom, m_posTo, m_sizeFrom, m_sizeTo;\n\n    float        m_lastDelta = 0;\n\n    enum eMode : uint8_t {\n        FLOAT_MODE_TOGGLE = 0,\n        FLOAT_MODE_FLOAT,\n        FLOAT_MODE_TILE,\n    };\n\n    eMode m_mode = FLOAT_MODE_TOGGLE;\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/FullscreenGesture.cpp",
    "content": "#include \"FullscreenGesture.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n#include \"../../../animation/DesktopAnimationManager.hpp\"\n\nconstexpr const float MAX_DISTANCE = 250.F;\n\n//\nstatic Vector2D lerpVal(const Vector2D& from, const Vector2D& to, const float& t) {\n    return Vector2D{\n        from.x + ((to.x - from.x) * t),\n        from.y + ((to.y - from.y) * t),\n    };\n}\n\nCFullscreenTrackpadGesture::CFullscreenTrackpadGesture(const std::string_view& mode) {\n    std::string lc = std::string{mode};\n    std::ranges::transform(lc, lc.begin(), ::tolower);\n\n    if (lc.starts_with(\"fullscreen\"))\n        m_mode = MODE_FULLSCREEN;\n    else if (lc.starts_with(\"maximize\"))\n        m_mode = MODE_MAXIMIZE;\n    else\n        m_mode = MODE_FULLSCREEN;\n}\n\nvoid CFullscreenTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    m_window = Desktop::focusState()->window();\n\n    if (!m_window)\n        return;\n\n    m_posFrom  = m_window->m_realPosition->goal();\n    m_sizeFrom = m_window->m_realSize->goal();\n\n    m_originalMode = m_window->m_fullscreenState.internal;\n\n    g_pCompositor->setWindowFullscreenInternal(m_window.lock(), m_window->m_fullscreenState.internal == FSMODE_NONE ? fsModeForMode(m_mode) : FSMODE_NONE);\n\n    m_posTo  = m_window->m_realPosition->goal();\n    m_sizeTo = m_window->m_realSize->goal();\n\n    m_lastDelta = 0.F;\n}\n\nvoid CFullscreenTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!m_window)\n        return;\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n\n    m_lastDelta += distance(e);\n\n    const auto FADEPERCENT = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    m_window->m_realPosition->setValueAndWarp(lerpVal(m_posFrom, m_posTo, FADEPERCENT));\n    m_window->m_realSize->setValueAndWarp(lerpVal(m_sizeFrom, m_sizeTo, FADEPERCENT));\n\n    g_pDesktopAnimationManager->overrideFullscreenFadeAmount(m_window->m_workspace, m_originalMode == FSMODE_NONE ? 1.F - FADEPERCENT : FADEPERCENT, m_window.lock());\n\n    g_pDecorationPositioner->onWindowUpdate(m_window.lock());\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n}\n\nvoid CFullscreenTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    if (!m_window)\n        return;\n\n    const auto COMPLETION = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    if (COMPLETION < 0.2F) {\n        // revert the animation\n        g_pHyprRenderer->damageWindow(m_window.lock());\n        g_pDesktopAnimationManager->overrideFullscreenFadeAmount(m_window->m_workspace, m_originalMode == FSMODE_NONE ? 1.F : 0.F, m_window.lock());\n        g_pCompositor->setWindowFullscreenInternal(m_window.lock(), m_window->m_fullscreenState.internal == FSMODE_NONE ? m_originalMode : FSMODE_NONE);\n        return;\n    }\n\n    *m_window->m_realPosition = m_posTo;\n    *m_window->m_realSize     = m_sizeTo;\n    g_pDesktopAnimationManager->overrideFullscreenFadeAmount(m_window->m_workspace, m_originalMode == FSMODE_NONE ? 0.F : 1.F);\n}\n\neFullscreenMode CFullscreenTrackpadGesture::fsModeForMode(eMode mode) {\n    switch (mode) {\n        case MODE_FULLSCREEN: return FSMODE_FULLSCREEN;\n        case MODE_MAXIMIZE: return FSMODE_MAXIMIZED;\n        default: break;\n    }\n    return FSMODE_FULLSCREEN;\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/FullscreenGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\n#include \"../../../../desktop/DesktopTypes.hpp\"\n#include \"../../../../desktop/Workspace.hpp\"\n\nclass CFullscreenTrackpadGesture : public ITrackpadGesture {\n  public:\n    CFullscreenTrackpadGesture(const std::string_view& mode);\n    virtual ~CFullscreenTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    PHLWINDOWREF m_window;\n\n    Vector2D     m_posFrom, m_posTo, m_sizeFrom, m_sizeTo;\n\n    float        m_lastDelta = 0;\n\n    enum eMode : uint8_t {\n        MODE_FULLSCREEN = 0,\n        MODE_MAXIMIZE,\n    };\n\n    eMode           m_mode         = MODE_FULLSCREEN;\n    eFullscreenMode m_originalMode = FSMODE_NONE;\n\n    eFullscreenMode fsModeForMode(eMode mode);\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/ITrackpadGesture.cpp",
    "content": "#include \"ITrackpadGesture.hpp\"\n\n// scale the pinch \"scale\" to match our imaginary delta units\nconstexpr const float PINCH_DELTA_SCALE         = 400.F;\nconstexpr const float PINCH_DELTA_SCALE_OUT_ADD = 1.6F;\n\n//\nvoid ITrackpadGesture::begin(const STrackpadGestureBegin& e) {\n    m_lastPinchScale = 1.F;\n    m_scale          = e.scale;\n}\n\nfloat ITrackpadGesture::distance(const STrackpadGestureBegin& e) {\n    if (e.direction == TRACKPAD_GESTURE_DIR_LEFT || e.direction == TRACKPAD_GESTURE_DIR_RIGHT || e.direction == TRACKPAD_GESTURE_DIR_HORIZONTAL)\n        return m_scale * (e.direction == TRACKPAD_GESTURE_DIR_LEFT ? -e.swipe->delta.x : e.swipe->delta.x);\n    if (e.direction == TRACKPAD_GESTURE_DIR_UP || e.direction == TRACKPAD_GESTURE_DIR_DOWN || e.direction == TRACKPAD_GESTURE_DIR_VERTICAL)\n        return m_scale * (e.direction == TRACKPAD_GESTURE_DIR_UP ? -e.swipe->delta.y : e.swipe->delta.y);\n    if (e.direction == TRACKPAD_GESTURE_DIR_SWIPE)\n        return m_scale * (e.swipe->delta.size());\n    if (e.direction == TRACKPAD_GESTURE_DIR_PINCH || e.direction == TRACKPAD_GESTURE_DIR_PINCH_IN || e.direction == TRACKPAD_GESTURE_DIR_PINCH_OUT) {\n        const auto Δ     = m_lastPinchScale - e.pinch->scale;\n        m_lastPinchScale = e.pinch->scale;\n        return m_scale * ((e.direction == TRACKPAD_GESTURE_DIR_PINCH_IN ? -Δ : Δ * PINCH_DELTA_SCALE_OUT_ADD) * PINCH_DELTA_SCALE);\n    }\n\n    return m_scale * (e.swipe ? e.swipe->delta.size() : e.pinch->delta.size());\n}\n\nfloat ITrackpadGesture::distance(const STrackpadGestureUpdate& e) {\n    return ITrackpadGesture::distance(STrackpadGestureBegin{\n        .swipe     = e.swipe,\n        .pinch     = e.pinch,\n        .direction = e.direction,\n        .scale     = e.scale,\n    });\n}\n\nbool ITrackpadGesture::isDirectionSensitive() {\n    return false;\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/ITrackpadGesture.hpp",
    "content": "#pragma once\n\n#include \"../../../../devices/IPointer.hpp\"\n#include \"../GestureTypes.hpp\"\n\nclass ITrackpadGesture {\n  public:\n    virtual ~ITrackpadGesture() = default;\n\n    struct STrackpadGestureBegin {\n        // this has update because we wait for the delta\n        const IPointer::SSwipeUpdateEvent* swipe     = nullptr;\n        const IPointer::SPinchUpdateEvent* pinch     = nullptr;\n        eTrackpadGestureDirection          direction = TRACKPAD_GESTURE_DIR_NONE;\n        float                              scale     = 1.F;\n    };\n\n    struct STrackpadGestureUpdate {\n        const IPointer::SSwipeUpdateEvent* swipe     = nullptr;\n        const IPointer::SPinchUpdateEvent* pinch     = nullptr;\n        eTrackpadGestureDirection          direction = TRACKPAD_GESTURE_DIR_NONE;\n        float                              scale     = 1.F;\n    };\n\n    struct STrackpadGestureEnd {\n        const IPointer::SSwipeEndEvent* swipe     = nullptr;\n        const IPointer::SPinchEndEvent* pinch     = nullptr;\n        eTrackpadGestureDirection       direction = TRACKPAD_GESTURE_DIR_NONE;\n        float                           scale     = 1.F;\n    };\n\n    virtual void  begin(const STrackpadGestureBegin& e);\n    virtual void  update(const STrackpadGestureUpdate& e) = 0;\n    virtual void  end(const STrackpadGestureEnd& e)       = 0;\n\n    virtual float distance(const STrackpadGestureBegin& e);\n    virtual float distance(const STrackpadGestureUpdate& e);\n\n    virtual bool  isDirectionSensitive();\n\n  protected:\n    float m_lastPinchScale = 1.F, m_scale = 1.F;\n};"
  },
  {
    "path": "src/managers/input/trackpad/gestures/MoveGesture.cpp",
    "content": "#include \"MoveGesture.hpp\"\n\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../desktop/view/Window.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n#include \"../../../../layout/LayoutManager.hpp\"\n\nvoid CMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    m_window    = Desktop::focusState()->window();\n    m_lastDelta = {};\n}\n\nvoid CMoveTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!m_window)\n        return;\n\n    const auto DELTA = e.swipe ? e.swipe->delta : e.pinch->delta;\n\n    if (m_window->m_isFloating) {\n        g_layoutManager->moveTarget(DELTA, m_window->layoutTarget());\n        m_window->m_realSize->warp();\n        m_window->m_realPosition->warp();\n        return;\n    }\n\n    // tiled window -> displace, then execute a move dispatcher on end.\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n\n    // funny name but works on tiled too lmao\n    m_lastDelta += DELTA;\n    m_window->m_floatingOffset = (m_lastDelta * 0.5F).clamp(Vector2D{-100.F, -100.F}, Vector2D{100.F, 100.F});\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n}\n\nvoid CMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n\n    if (!m_window)\n        return;\n\n    if (m_window->m_isFloating || m_lastDelta.size() < 0.1F)\n        return;\n\n    // tiled: attempt to move window in the given direction\n\n    const auto WINDOWPOS = m_window->m_realPosition->goal() + m_window->m_floatingOffset;\n\n    m_window->m_floatingOffset = {};\n\n    if (std::abs(m_lastDelta.x) > std::abs(m_lastDelta.y)) {\n        // horizontal\n        g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.x > 0 ? \"r\" : \"l\");\n    } else {\n        // vertical\n        g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.y > 0 ? \"b\" : \"t\");\n    }\n\n    const auto GOAL = m_window->m_realPosition->goal();\n\n    m_window->m_realPosition->setValueAndWarp(WINDOWPOS);\n    *m_window->m_realPosition = GOAL;\n\n    m_window.reset();\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/MoveGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\n#include \"../../../../desktop/DesktopTypes.hpp\"\n\nclass CMoveTrackpadGesture : public ITrackpadGesture {\n  public:\n    CMoveTrackpadGesture()          = default;\n    virtual ~CMoveTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    PHLWINDOWREF m_window;\n    Vector2D     m_lastDelta;\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/ResizeGesture.cpp",
    "content": "#include \"ResizeGesture.hpp\"\n\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../desktop/view/Window.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n#include \"../../../../layout/LayoutManager.hpp\"\n\nvoid CResizeTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    m_window = Desktop::focusState()->window();\n}\n\nvoid CResizeTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!m_window)\n        return;\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n\n    g_layoutManager->resizeTarget((e.swipe ? e.swipe->delta : e.pinch->delta), m_window->layoutTarget(),\n                                  Layout::cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal()));\n    m_window->m_realSize->warp();\n    m_window->m_realPosition->warp();\n\n    g_pHyprRenderer->damageWindow(m_window.lock());\n}\n\nvoid CResizeTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    m_window.reset();\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/ResizeGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\n#include \"../../../../desktop/DesktopTypes.hpp\"\n\nclass CResizeTrackpadGesture : public ITrackpadGesture {\n  public:\n    CResizeTrackpadGesture()          = default;\n    virtual ~CResizeTrackpadGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    PHLWINDOWREF m_window;\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/SpecialWorkspaceGesture.cpp",
    "content": "#include \"SpecialWorkspaceGesture.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n\n#include <hyprutils/memory/Casts.hpp>\nusing namespace Hyprutils::Memory;\n\nconstexpr const float MAX_DISTANCE = 150.F;\n\n//\nstatic Vector2D lerpVal(const Vector2D& from, const Vector2D& to, const float& t) {\n    return Vector2D{\n        from.x + ((to.x - from.x) * t),\n        from.y + ((to.y - from.y) * t),\n    };\n}\n\nstatic float lerpVal(const float& from, const float& to, const float& t) {\n    return from + ((to - from) * t);\n}\n\nCSpecialWorkspaceGesture::CSpecialWorkspaceGesture(const std::string& workspaceName) : m_specialWorkspaceName(workspaceName) {\n    ;\n}\n\nvoid CSpecialWorkspaceGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    m_specialWorkspace.reset();\n    m_lastDelta = 0.F;\n    m_monitor.reset();\n\n    m_specialWorkspace = g_pCompositor->getWorkspaceByName(\"special:\" + m_specialWorkspaceName);\n\n    if (m_specialWorkspace) {\n        m_animatingOut = m_specialWorkspace->isVisible();\n        m_monitor      = m_animatingOut ? m_specialWorkspace->m_monitor : Desktop::focusState()->monitor();\n\n        if (!m_monitor)\n            return;\n\n        if (!m_animatingOut)\n            m_monitor->setSpecialWorkspace(m_specialWorkspace);\n    } else {\n        m_monitor = Desktop::focusState()->monitor();\n\n        if (!m_monitor)\n            return;\n\n        m_animatingOut = false;\n\n        const auto& [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(\"special:\" + m_specialWorkspaceName);\n        const auto WS                                      = g_pCompositor->createNewWorkspace(workspaceID, m_monitor->m_id, workspaceName);\n        m_monitor->setSpecialWorkspace(WS);\n        m_specialWorkspace = WS;\n    }\n\n    if (!m_specialWorkspace)\n        return;\n\n    m_monitorDimFrom      = m_monitor->m_specialFade->begun();\n    m_monitorDimTo        = m_monitor->m_specialFade->goal();\n    m_workspaceAlphaFrom  = m_specialWorkspace->m_alpha->begun();\n    m_workspaceAlphaTo    = m_specialWorkspace->m_alpha->goal();\n    m_workspaceOffsetFrom = m_specialWorkspace->m_renderOffset->begun();\n    m_workspaceOffsetTo   = m_specialWorkspace->m_renderOffset->goal();\n}\n\nvoid CSpecialWorkspaceGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!m_specialWorkspace || !m_monitor)\n        return;\n\n    g_pHyprRenderer->damageMonitor(m_specialWorkspace->m_monitor.lock());\n\n    m_lastDelta += distance(e);\n\n    const auto FADEPERCENT = m_animatingOut ? 1.F - std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F) : std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    m_monitor->m_specialFade->setValueAndWarp(lerpVal(m_monitorDimFrom, m_monitorDimTo, FADEPERCENT));\n    m_specialWorkspace->m_alpha->setValueAndWarp(lerpVal(m_workspaceAlphaFrom, m_workspaceAlphaTo, FADEPERCENT));\n    m_specialWorkspace->m_renderOffset->setValueAndWarp(lerpVal(m_workspaceOffsetFrom, m_workspaceOffsetTo, FADEPERCENT));\n}\n\nvoid CSpecialWorkspaceGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    if (!m_specialWorkspace || !m_monitor)\n        return;\n\n    const auto COMPLETION = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);\n\n    if (COMPLETION < 0.3F) {\n        // cancel the operation, which effectively means just flip the animation direction\n        // also flip goals if animating in\n        m_animatingOut = !m_animatingOut;\n\n        if (m_animatingOut) {\n            m_workspaceOffsetTo = m_workspaceOffsetFrom;\n            m_workspaceAlphaTo  = m_workspaceAlphaFrom;\n            m_monitorDimTo      = m_monitorDimFrom;\n        }\n    }\n\n    if (m_animatingOut) {\n        const auto CURR_WS_ALPHA  = m_specialWorkspace->m_alpha->value();\n        const auto CURR_WS_OFFSET = m_specialWorkspace->m_renderOffset->value();\n        const auto CURR_MON_FADE  = m_monitor->m_specialFade->value();\n\n        m_monitor->setSpecialWorkspace(nullptr);\n\n        const auto GOAL_WS_ALPHA  = m_specialWorkspace->m_alpha->goal();\n        const auto GOAL_WS_OFFSET = m_specialWorkspace->m_renderOffset->goal();\n\n        m_monitor->m_specialFade->setValueAndWarp(CURR_MON_FADE);\n        m_specialWorkspace->m_alpha->setValueAndWarp(CURR_WS_ALPHA);\n        m_specialWorkspace->m_renderOffset->setValueAndWarp(CURR_WS_OFFSET);\n\n        *m_monitor->m_specialFade           = 0.F;\n        *m_specialWorkspace->m_alpha        = GOAL_WS_ALPHA;\n        *m_specialWorkspace->m_renderOffset = GOAL_WS_OFFSET;\n    } else {\n        *m_monitor->m_specialFade           = m_monitorDimTo;\n        *m_specialWorkspace->m_renderOffset = m_workspaceOffsetTo;\n        *m_specialWorkspace->m_alpha        = m_workspaceAlphaTo;\n    }\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/SpecialWorkspaceGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n\n#include \"../../../../desktop/DesktopTypes.hpp\"\n\nclass CSpecialWorkspaceGesture : public ITrackpadGesture {\n  public:\n    CSpecialWorkspaceGesture(const std::string& workspaceName);\n    virtual ~CSpecialWorkspaceGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n  private:\n    std::string   m_specialWorkspaceName;\n    PHLWORKSPACE  m_specialWorkspace;\n    PHLMONITORREF m_monitor;\n    bool          m_animatingOut = false;\n    float         m_lastDelta    = 0.F;\n\n    // animated properties, kinda sucks\n    float    m_monitorDimFrom = 0.F, m_monitorDimTo = 0.F;\n    float    m_workspaceAlphaFrom = 0.F, m_workspaceAlphaTo = 0.F;\n    Vector2D m_workspaceOffsetFrom = {}, m_workspaceOffsetTo = {};\n};\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/WorkspaceSwipeGesture.cpp",
    "content": "#include \"WorkspaceSwipeGesture.hpp\"\n\n#include \"../../../../Compositor.hpp\"\n#include \"../../../../desktop/state/FocusState.hpp\"\n#include \"../../../../render/Renderer.hpp\"\n\n#include \"../../UnifiedWorkspaceSwipeGesture.hpp\"\n\nvoid CWorkspaceSwipeGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {\n    ITrackpadGesture::begin(e);\n\n    static auto PSWIPENEW = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_create_new\");\n\n    if (g_pSessionLockManager->isSessionLocked() || g_pUnifiedWorkspaceSwipe->isGestureInProgress())\n        return;\n\n    int onMonitor = 0;\n    for (auto const& w : g_pCompositor->getWorkspaces()) {\n        if (w->m_monitor == Desktop::focusState()->monitor() && !g_pCompositor->isWorkspaceSpecial(w->m_id))\n            onMonitor++;\n    }\n\n    if (onMonitor < 2 && !*PSWIPENEW)\n        return; // disallow swiping when there's 1 workspace on a monitor\n\n    g_pUnifiedWorkspaceSwipe->begin();\n}\n\nvoid CWorkspaceSwipeGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {\n    if (!g_pUnifiedWorkspaceSwipe->isGestureInProgress())\n        return;\n\n    const float  DELTA = distance(e);\n\n    static auto  PSWIPEINVR = CConfigValue<Hyprlang::INT>(\"gestures:workspace_swipe_invert\");\n\n    const double D = g_pUnifiedWorkspaceSwipe->m_delta + (*PSWIPEINVR ? -DELTA : DELTA);\n    g_pUnifiedWorkspaceSwipe->update(D);\n}\n\nvoid CWorkspaceSwipeGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {\n    if (!g_pUnifiedWorkspaceSwipe->isGestureInProgress())\n        return;\n\n    g_pUnifiedWorkspaceSwipe->end();\n}\n\nbool CWorkspaceSwipeGesture::isDirectionSensitive() {\n    return true;\n}\n"
  },
  {
    "path": "src/managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp",
    "content": "#pragma once\n\n#include \"ITrackpadGesture.hpp\"\n#include \"../../../../desktop/DesktopTypes.hpp\"\n\nclass CWorkspaceSwipeGesture : public ITrackpadGesture {\n  public:\n    CWorkspaceSwipeGesture()          = default;\n    virtual ~CWorkspaceSwipeGesture() = default;\n\n    virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e);\n    virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e);\n    virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e);\n\n    virtual bool isDirectionSensitive();\n};\n"
  },
  {
    "path": "src/managers/permissions/DynamicPermissionManager.cpp",
    "content": "#include <re2/re2.h>\n#include \"DynamicPermissionManager.hpp\"\n#include <algorithm>\n#include <wayland-server-core.h>\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../helpers/MiscFunctions.hpp\"\n#include \"../../i18n/Engine.hpp\"\n\n#include <hyprutils/string/String.hpp>\nusing namespace Hyprutils::String;\n\n#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)\n#include <sys/sysctl.h>\n#endif\n\nstatic void clientDestroyInternal(struct wl_listener* listener, void* data) {\n    SDynamicPermissionRuleDestroyWrapper* wrap = wl_container_of(listener, wrap, listener);\n    CDynamicPermissionRule*               rule = wrap->parent;\n    g_pDynamicPermissionManager->removeRulesForClient(rule->client());\n}\n\nCDynamicPermissionRule::CDynamicPermissionRule(const std::string& binaryPathRegex, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode) :\n    m_type(type), m_source(PERMISSION_RULE_SOURCE_CONFIG), m_binaryRegex(makeUnique<re2::RE2>(binaryPathRegex)), m_allowMode(defaultAllowMode) {\n    ;\n}\n\nCDynamicPermissionRule::CDynamicPermissionRule(wl_client* const client, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode) :\n    m_type(type), m_source(PERMISSION_RULE_SOURCE_RUNTIME_USER), m_client(client), m_allowMode(defaultAllowMode) {\n    wl_list_init(&m_destroyWrapper.listener.link);\n    m_destroyWrapper.listener.notify = ::clientDestroyInternal;\n    m_destroyWrapper.parent          = this;\n    wl_display_add_destroy_listener(g_pCompositor->m_wlDisplay, &m_destroyWrapper.listener);\n}\n\nCDynamicPermissionRule::~CDynamicPermissionRule() {\n    if (m_client) {\n        wl_list_remove(&m_destroyWrapper.listener.link);\n        wl_list_init(&m_destroyWrapper.listener.link);\n    }\n\n    if (m_dialogBox && m_dialogBox->isRunning())\n        m_dialogBox->kill();\n}\n\nwl_client* CDynamicPermissionRule::client() const {\n    return m_client;\n}\n\nstatic const char* permissionToString(eDynamicPermissionType type) {\n    switch (type) {\n        case PERMISSION_TYPE_UNKNOWN: return \"PERMISSION_TYPE_UNKNOWN\";\n        case PERMISSION_TYPE_SCREENCOPY: return \"PERMISSION_TYPE_SCREENCOPY\";\n        case PERMISSION_TYPE_PLUGIN: return \"PERMISSION_TYPE_PLUGIN\";\n        case PERMISSION_TYPE_KEYBOARD: return \"PERMISSION_TYPE_KEYBOARD\";\n        case PERMISSION_TYPE_CURSOR_POS: return \"PERMISSION_TYPE_CURSOR_POS\";\n    }\n\n    return \"error\";\n}\n\nstatic const char* specialPidToString(eSpecialPidTypes type) {\n    switch (type) {\n        case SPECIAL_PID_TYPE_CONFIG: return \"config\";\n        default: return \"\";\n    }\n}\n\nvoid CDynamicPermissionManager::clearConfigPermissions() {\n    std::erase_if(m_rules, [](const auto& e) { return e->m_source == PERMISSION_RULE_SOURCE_CONFIG; });\n}\n\nvoid CDynamicPermissionManager::addConfigPermissionRule(const std::string& binaryName, eDynamicPermissionType type, eDynamicPermissionAllowMode mode) {\n    m_rules.emplace_back(SP<CDynamicPermissionRule>(new CDynamicPermissionRule(binaryName, type, mode)));\n}\n\neDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionMode(wl_client* client, eDynamicPermissionType permission) {\n\n    static auto PPERM = CConfigValue<Hyprlang::INT>(\"ecosystem:enforce_permissions\");\n\n    if (*PPERM == 0)\n        return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n\n    const auto LOOKUP = binaryNameForWlClient(client);\n\n    Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: checking permission {} for client {:x} (binary {})\", permissionToString(permission),\n                     rc<uintptr_t>(client), LOOKUP.has_value() ? LOOKUP.value() : \"lookup failed: \" + LOOKUP.error());\n\n    // first, check if we have the client + perm combo in our cache.\n    auto it = std::ranges::find_if(m_rules, [client, permission](const auto& e) { return e->m_client == client && e->m_type == permission; });\n    if (it == m_rules.end()) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission not cached, checking binary name\");\n\n        if (!LOOKUP.has_value())\n            Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: binary name check failed\");\n        else {\n            const auto BINNAME = LOOKUP.value().contains(\"/\") ? LOOKUP.value().substr(LOOKUP.value().find_last_of('/') + 1) : LOOKUP.value();\n            Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: binary path {}, name {}\", LOOKUP.value(), BINNAME);\n\n            it = std::ranges::find_if(m_rules, [clientBinaryPath = LOOKUP.value(), permission](const auto& e) {\n                if (e->m_type != permission)\n                    return false; // wrong perm\n\n                if (!e->m_binaryPath.empty() && e->m_binaryPath == clientBinaryPath)\n                    return true; // matches binary path\n\n                if (!e->m_binaryRegex)\n                    return false; // wl_client* rule\n\n                // regex match\n                if (RE2::FullMatch(clientBinaryPath, *e->m_binaryRegex))\n                    return true;\n\n                return false;\n            });\n\n            if (it == m_rules.end())\n                Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: no rule for binary\");\n            else {\n                if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) {\n                    Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission allowed by config rule\");\n                    return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n                } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) {\n                    Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission denied by config rule\");\n                    return PERMISSION_RULE_ALLOW_MODE_DENY;\n                } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n                    Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission pending by config rule\");\n                    return PERMISSION_RULE_ALLOW_MODE_PENDING;\n                } else\n                    Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission ask by config rule\");\n            }\n        }\n    } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission allowed before by user\");\n        return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n    } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission denied before by user\");\n        return PERMISSION_RULE_ALLOW_MODE_DENY;\n    } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission pending before by user\");\n        return PERMISSION_RULE_ALLOW_MODE_PENDING;\n    }\n\n    // if we are here, we need to ask, that's the fallback for all these (keyboards won't come here)\n    askForPermission(client, LOOKUP.value_or(\"\"), permission);\n\n    return PERMISSION_RULE_ALLOW_MODE_PENDING;\n}\n\neDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionModeWithString(pid_t pid, const std::string& str, eDynamicPermissionType permission) {\n    static auto PPERM = CConfigValue<Hyprlang::INT>(\"ecosystem:enforce_permissions\");\n\n    if (*PPERM == 0)\n        return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n\n    std::optional<std::string>              binaryName;\n    std::expected<std::string, std::string> lookup;\n\n    if (pid > 0) {\n        lookup = binaryNameForPid(pid);\n\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: checking permission {} for key {} (binary {})\", permissionToString(permission), str,\n                         lookup.has_value() ? lookup.value() : \"lookup failed: \" + lookup.error());\n\n        if (lookup.has_value())\n            binaryName = *lookup;\n    } else\n        binaryName = specialPidToString(sc<eSpecialPidTypes>(pid));\n\n    // first, check if we have the client + perm combo in our cache.\n    auto it = std::ranges::find_if(m_rules, [str, permission, pid](const auto& e) { return e->m_keyString == str && pid && pid == e->m_pid && e->m_type == permission; });\n    if (it == m_rules.end()) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission not cached, checking key\");\n\n        it = std::ranges::find_if(m_rules, [key = str, permission, &lookup](const auto& e) {\n            if (e->m_type != permission)\n                return false; // wrong perm\n\n            if (!e->m_binaryRegex)\n                return false; // no regex\n\n            // regex match\n            if (RE2::FullMatch(key, *e->m_binaryRegex) || (lookup.has_value() && RE2::FullMatch(lookup.value(), *e->m_binaryRegex)))\n                return true;\n\n            return false;\n        });\n\n        if (it == m_rules.end())\n            Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: no rule for key\");\n        else {\n            if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) {\n                Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission allowed by config rule\");\n                return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n            } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) {\n                Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission denied by config rule\");\n                return PERMISSION_RULE_ALLOW_MODE_DENY;\n            } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n                Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission pending by config rule\");\n                return PERMISSION_RULE_ALLOW_MODE_PENDING;\n            } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ASK) {\n                Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission ask by config rule\");\n                askForPermission(nullptr, str, permission, pid);\n                return PERMISSION_RULE_ALLOW_MODE_PENDING;\n            } else\n                Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission ask by config rule\");\n        }\n\n    } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission allowed before by user\");\n        return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n    } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission denied before by user\");\n        return PERMISSION_RULE_ALLOW_MODE_DENY;\n    } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionManager::clientHasPermission: permission pending before by user\");\n        return PERMISSION_RULE_ALLOW_MODE_PENDING;\n    }\n\n    // keyboards are allow default\n    if (permission == PERMISSION_TYPE_KEYBOARD)\n        return PERMISSION_RULE_ALLOW_MODE_ALLOW;\n\n    // if we are here, we need to ask.\n    askForPermission(nullptr, str, permission, pid);\n\n    return PERMISSION_RULE_ALLOW_MODE_PENDING;\n}\n\nvoid CDynamicPermissionManager::askForPermission(wl_client* client, const std::string& binaryPath, eDynamicPermissionType type, pid_t pid) {\n    auto rule = m_rules.emplace_back(SP<CDynamicPermissionRule>(new CDynamicPermissionRule(client, type, PERMISSION_RULE_ALLOW_MODE_PENDING)));\n\n    if (!client)\n        rule->m_keyString = binaryPath;\n\n    rule->m_pid = pid;\n\n    std::string appName = \"\";\n    if (binaryPath.empty())\n        appName = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, {{\"wayland_id\", std::format(\"{:x}\", rc<uintptr_t>(client))}});\n    else if (client) {\n        appName = binaryPath.contains(\"/\") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath;\n    } else {\n        if (pid < 0)\n            appName = specialPidToString(sc<eSpecialPidTypes>(pid));\n        else {\n            const auto LOOKUP = binaryNameForPid(pid);\n            appName           = LOOKUP.value_or(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_NAME));\n        }\n    }\n\n    std::string description = \"\";\n    switch (rule->m_type) {\n        case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{\"app\", appName}}); break;\n        case PERMISSION_TYPE_CURSOR_POS: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, {{\"app\", appName}}); break;\n        case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_PLUGIN, {{\"app\", appName}, {\"plugin\", binaryPath}}); break;\n        case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{\"keyboard\", binaryPath}}); break;\n        case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{\"app\", appName}}); break;\n    }\n\n    std::vector<std::string> options;\n    const auto               ALLOW              = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW);\n    const auto               ALLOW_AND_REMEMBER = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER);\n    const auto               ALLOW_ONCE         = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_ONCE);\n    const auto               DENY               = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_DENY);\n\n    if (!binaryPath.empty() && client) {\n        description += std::format(\"<br/><br/><i>{}</i>\", I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_PERSISTENCE_HINT));\n        options = {DENY, ALLOW_AND_REMEMBER, ALLOW_ONCE};\n    } else\n        options = {DENY, ALLOW};\n\n    rule->m_dialogBox             = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_TITLE), description, options);\n    rule->m_dialogBox->m_priority = true;\n\n    if (!rule->m_dialogBox) {\n        Log::logger->log(Log::ERR, \"CDynamicPermissionManager::askForPermission: hyprland-guiutils likely missing, cannot ask! Disabling permission control...\");\n        rule->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;\n        return;\n    }\n\n    rule->m_promise = rule->m_dialogBox->open();\n    rule->m_promise->then([r = WP<CDynamicPermissionRule>(rule), binaryPath, ALLOW, ALLOW_AND_REMEMBER, ALLOW_ONCE, DENY](SP<CPromiseResult<std::string>> pr) {\n        if (!r)\n            return;\n\n        if (pr->hasError()) {\n            // not reachable for now\n            Log::logger->log(Log::TRACE, \"CDynamicPermissionRule: error spawning dialog box\");\n            if (r->m_promiseResolverForExternal)\n                r->m_promiseResolverForExternal->reject(\"error spawning dialog box\");\n            r->m_promiseResolverForExternal.reset();\n            return;\n        }\n\n        const std::string& result = pr->result();\n\n        Log::logger->log(Log::TRACE, \"CDynamicPermissionRule: user returned {}\", result);\n\n        if (result.starts_with(ALLOW_ONCE))\n            r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;\n        else if (result.starts_with(DENY)) {\n            r->m_allowMode  = PERMISSION_RULE_ALLOW_MODE_DENY;\n            r->m_binaryPath = binaryPath;\n        } else if (result.starts_with(ALLOW_AND_REMEMBER)) {\n            r->m_allowMode  = PERMISSION_RULE_ALLOW_MODE_ALLOW;\n            r->m_binaryPath = binaryPath;\n        } else if (result.starts_with(ALLOW))\n            r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;\n\n        if (r->m_promiseResolverForExternal)\n            r->m_promiseResolverForExternal->resolve(r->m_allowMode);\n\n        r->m_promise.reset();\n        r->m_promiseResolverForExternal.reset();\n    });\n}\n\nSP<CPromise<eDynamicPermissionAllowMode>> CDynamicPermissionManager::promiseFor(wl_client* client, eDynamicPermissionType permission) {\n    auto rule = std::ranges::find_if(m_rules, [&client, &permission](const auto& e) { return e->m_client == client && e->m_type == permission; });\n    if (rule == m_rules.end())\n        return nullptr;\n\n    if (!(*rule)->m_promise)\n        return nullptr;\n\n    if ((*rule)->m_promiseResolverForExternal)\n        return nullptr;\n\n    return CPromise<eDynamicPermissionAllowMode>::make([rule](SP<CPromiseResolver<eDynamicPermissionAllowMode>> r) { (*rule)->m_promiseResolverForExternal = r; });\n}\n\nSP<CPromise<eDynamicPermissionAllowMode>> CDynamicPermissionManager::promiseFor(const std::string& key, eDynamicPermissionType permission) {\n    auto rule = std::ranges::find_if(m_rules, [&key, &permission](const auto& e) { return e->m_keyString == key && e->m_type == permission; });\n    if (rule == m_rules.end())\n        return nullptr;\n\n    if (!(*rule)->m_promise)\n        return nullptr;\n\n    if ((*rule)->m_promiseResolverForExternal)\n        return nullptr;\n\n    return CPromise<eDynamicPermissionAllowMode>::make([rule](SP<CPromiseResolver<eDynamicPermissionAllowMode>> r) { (*rule)->m_promiseResolverForExternal = r; });\n}\n\nSP<CPromise<eDynamicPermissionAllowMode>> CDynamicPermissionManager::promiseFor(pid_t pid, const std::string& key, eDynamicPermissionType permission) {\n    auto rule = std::ranges::find_if(m_rules, [&pid, &permission, &key](const auto& e) { return e->m_pid == pid && e->m_keyString == key && e->m_type == permission; });\n    if (rule == m_rules.end())\n        return nullptr;\n\n    if (!(*rule)->m_promise)\n        return nullptr;\n\n    if ((*rule)->m_promiseResolverForExternal)\n        return nullptr;\n\n    return CPromise<eDynamicPermissionAllowMode>::make([rule](SP<CPromiseResolver<eDynamicPermissionAllowMode>> r) { (*rule)->m_promiseResolverForExternal = r; });\n}\n\nvoid CDynamicPermissionManager::removeRulesForClient(wl_client* client) {\n    std::erase_if(m_rules, [client](const auto& e) { return e->m_client == client; });\n}\n"
  },
  {
    "path": "src/managers/permissions/DynamicPermissionManager.hpp",
    "content": "#pragma once\n\n#include \"../../macros.hpp\"\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/AsyncDialogBox.hpp\"\n#include <vector>\n#include <wayland-server-core.h>\n#include <sys/types.h>\n#include \"../../helpers/defer/Promise.hpp\"\n\n// NOLINTNEXTLINE\nnamespace re2 {\n    class RE2;\n};\n\nenum eDynamicPermissionType : uint8_t {\n    PERMISSION_TYPE_UNKNOWN = 0,\n    PERMISSION_TYPE_SCREENCOPY,\n    PERMISSION_TYPE_PLUGIN,\n    PERMISSION_TYPE_KEYBOARD,\n    PERMISSION_TYPE_CURSOR_POS,\n};\n\nenum eDynamicPermissionRuleSource : uint8_t {\n    PERMISSION_RULE_SOURCE_UNKNOWN = 0,\n    PERMISSION_RULE_SOURCE_CONFIG,\n    PERMISSION_RULE_SOURCE_RUNTIME_USER,\n};\n\nenum eDynamicPermissionAllowMode : uint8_t {\n    PERMISSION_RULE_ALLOW_MODE_UNKNOWN = 0,\n    PERMISSION_RULE_ALLOW_MODE_DENY,\n    PERMISSION_RULE_ALLOW_MODE_ASK,\n    PERMISSION_RULE_ALLOW_MODE_ALLOW,\n    PERMISSION_RULE_ALLOW_MODE_PENDING, // popup is open\n};\n\n// NOLINTNEXTLINE\nenum eSpecialPidTypes : int {\n    SPECIAL_PID_TYPE_CONFIG = -3,\n    SPECIAL_PID_TYPE_NONE   = -2,\n};\n\nclass CDynamicPermissionRule;\n\nstruct SDynamicPermissionRuleDestroyWrapper {\n    wl_listener             listener;\n    CDynamicPermissionRule* parent = nullptr;\n};\n\nclass CDynamicPermissionRule {\n  public:\n    ~CDynamicPermissionRule();\n\n    wl_client* client() const;\n\n  private:\n    // config rule\n    CDynamicPermissionRule(const std::string& binaryPathRegex, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode = PERMISSION_RULE_ALLOW_MODE_ASK);\n    // user rule\n    CDynamicPermissionRule(wl_client* const client, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode = PERMISSION_RULE_ALLOW_MODE_ASK);\n\n    const eDynamicPermissionType                      m_type       = PERMISSION_TYPE_UNKNOWN;\n    const eDynamicPermissionRuleSource                m_source     = PERMISSION_RULE_SOURCE_UNKNOWN;\n    wl_client* const                                  m_client     = nullptr;\n    std::string                                       m_binaryPath = \"\";\n    UP<re2::RE2>                                      m_binaryRegex;\n    std::string                                       m_keyString = \"\";\n    pid_t                                             m_pid       = 0;\n\n    eDynamicPermissionAllowMode                       m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK;\n    SP<CAsyncDialogBox>                               m_dialogBox;                  // for pending\n    SP<CPromise<std::string>>                         m_promise;                    // for pending\n    SP<CPromiseResolver<eDynamicPermissionAllowMode>> m_promiseResolverForExternal; // for external promise\n\n    SDynamicPermissionRuleDestroyWrapper              m_destroyWrapper;\n\n    friend class CDynamicPermissionManager;\n};\n\nclass CDynamicPermissionManager {\n  public:\n    void clearConfigPermissions();\n    void addConfigPermissionRule(const std::string& binaryPath, eDynamicPermissionType type, eDynamicPermissionAllowMode mode);\n\n    // if the rule is \"ask\", or missing, will pop up a dialog and return false until the user agrees.\n    // (will continue returning false if the user does not agree, of course.)\n    eDynamicPermissionAllowMode clientPermissionMode(wl_client* client, eDynamicPermissionType permission);\n\n    // for plugins for now. Pid 0 means unknown\n    eDynamicPermissionAllowMode clientPermissionModeWithString(pid_t pid, const std::string& str, eDynamicPermissionType permission);\n\n    // get a promise for the result. Returns null if there already was one requested for the client.\n    // Returns null if state is not pending\n    SP<CPromise<eDynamicPermissionAllowMode>> promiseFor(wl_client* client, eDynamicPermissionType permission);\n    SP<CPromise<eDynamicPermissionAllowMode>> promiseFor(const std::string& str, eDynamicPermissionType permission);\n    SP<CPromise<eDynamicPermissionAllowMode>> promiseFor(pid_t pid, const std::string& key, eDynamicPermissionType permission);\n\n    void                                      removeRulesForClient(wl_client* client);\n\n  private:\n    void askForPermission(wl_client* client, const std::string& binaryName, eDynamicPermissionType type, pid_t pid = 0);\n\n    //\n    std::vector<SP<CDynamicPermissionRule>> m_rules;\n};\n\ninline UP<CDynamicPermissionManager> g_pDynamicPermissionManager;\n"
  },
  {
    "path": "src/managers/screenshare/CursorshareSession.cpp",
    "content": "#include \"ScreenshareManager.hpp\"\n#include \"../PointerManager.hpp\"\n#include \"../../protocols/core/Seat.hpp\"\n#include \"../permissions/DynamicPermissionManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"render/pass/TexPassElement.hpp\"\n\nusing namespace Screenshare;\n\nCCursorshareSession::CCursorshareSession(wl_client* client, WP<CWLPointerResource> pointer) : m_client(client), m_pointer(pointer) {\n    m_listeners.pointerDestroyed = m_pointer->m_events.destroyed.listen([this] { stop(); });\n    m_listeners.cursorChanged    = g_pPointerManager->m_events.cursorChanged.listen([this] {\n        calculateConstraints();\n        m_events.constraintsChanged.emit();\n\n        if (m_pendingFrame.pending) {\n            if (copy())\n                return;\n\n            LOGM(Log::ERR, \"Failed to copy cursor image for cursor share\");\n            if (m_pendingFrame.callback)\n                m_pendingFrame.callback(RESULT_NOT_COPIED);\n            m_pendingFrame.pending = false;\n            return;\n        }\n    });\n\n    calculateConstraints();\n}\n\nCCursorshareSession::~CCursorshareSession() {\n    stop();\n}\n\nvoid CCursorshareSession::stop() {\n    if (m_stopped)\n        return;\n    m_stopped = true;\n    m_events.stopped.emit();\n}\n\nvoid CCursorshareSession::calculateConstraints() {\n    const auto& cursorImage = g_pPointerManager->currentCursorImage();\n    m_constraintsChanged    = true;\n\n    // cursor is hidden, keep the previous constraints and render 0 alpha\n    if (!cursorImage.pBuffer)\n        return;\n\n    // TODO: should cursor share have a format bit flip for RGBA?\n    if (auto attrs = cursorImage.pBuffer->shm(); attrs.success) {\n        m_format = attrs.format;\n    } else {\n        // we only have shm cursors\n        return;\n    }\n\n    m_hotspot    = cursorImage.hotspot;\n    m_bufferSize = cursorImage.size;\n}\n\n// TODO: allow render to buffer without monitor and remove monitor param\neScreenshareError CCursorshareSession::share(PHLMONITOR monitor, SP<IHLBuffer> buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback) {\n    if (m_stopped || m_pointer.expired() || m_bufferSize == Vector2D(0, 0))\n        return ERROR_STOPPED;\n\n    if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) {\n        LOGM(Log::ERR, \"Client requested sharing to an invalid buffer\");\n        return ERROR_NO_BUFFER;\n    }\n\n    if UNLIKELY (buffer->size != m_bufferSize) {\n        LOGM(Log::ERR, \"Client requested sharing to an invalid buffer size\");\n        return ERROR_BUFFER_SIZE;\n    }\n\n    uint32_t bufFormat;\n    if (buffer->dmabuf().success)\n        bufFormat = buffer->dmabuf().format;\n    else if (buffer->shm().success)\n        bufFormat = buffer->shm().format;\n    else {\n        LOGM(Log::ERR, \"Client requested sharing to an invalid buffer\");\n        return ERROR_NO_BUFFER;\n    }\n\n    if (bufFormat != m_format) {\n        LOGM(Log::ERR, \"Invalid format {} in {:x}\", bufFormat, (uintptr_t)this);\n        return ERROR_BUFFER_FORMAT;\n    }\n\n    m_pendingFrame.pending           = true;\n    m_pendingFrame.monitor           = monitor;\n    m_pendingFrame.buffer            = buffer;\n    m_pendingFrame.sourceBoxCallback = sourceBoxCallback;\n    m_pendingFrame.callback          = callback;\n\n    // nothing changed, then delay copy until contraints changed\n    if (!m_constraintsChanged)\n        return ERROR_NONE;\n\n    if (!copy()) {\n        LOGM(Log::ERR, \"Failed to copy cursor image for cursor share\");\n        callback(RESULT_NOT_COPIED);\n        m_pendingFrame.pending = false;\n        return ERROR_UNKNOWN;\n    }\n\n    return ERROR_NONE;\n}\n\nvoid CCursorshareSession::render() {\n    const auto  PERM = g_pDynamicPermissionManager->clientPermissionMode(m_client, PERMISSION_TYPE_CURSOR_POS);\n\n    const auto& cursorImage = g_pPointerManager->currentCursorImage();\n\n    // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that\n    g_pHyprRenderer->m_renderData.transformDamage = false;\n    g_pHyprRenderer->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y);\n\n    bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(m_pendingFrame.sourceBoxCallback());\n    g_pHyprRenderer->startRenderPass();\n    if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW || !overlaps) {\n        // render black when not allowed\n        g_pHyprRenderer->draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{Colors::BLACK}), {});\n    } else if (!cursorImage.pBuffer || !cursorImage.surface || !cursorImage.bufferTex) {\n        // render clear when cursor is probably hidden\n        g_pHyprRenderer->draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), {});\n    } else {\n        // render cursor\n        CBox texbox = {{}, cursorImage.bufferTex->m_size};\n        g_pHyprRenderer->draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                                  .tex = cursorImage.bufferTex,\n                                  .box = texbox,\n                              }),\n                              {});\n    }\n\n    g_pHyprRenderer->m_renderData.blockScreenShader = true;\n}\n\nbool CCursorshareSession::copy() {\n    if (!m_pendingFrame.callback || !m_pendingFrame.monitor || !m_pendingFrame.callback || !m_pendingFrame.sourceBoxCallback)\n        return false;\n\n    // FIXME: this doesn't really make sense but just to be safe\n    m_pendingFrame.callback(RESULT_TIMESTAMP);\n\n    CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};\n    if (auto attrs = m_pendingFrame.buffer->dmabuf(); attrs.success) {\n        if (attrs.format != m_format) {\n            LOGM(Log::ERR, \"Can't copy: invalid format\");\n            return false;\n        }\n\n        if (!g_pHyprRenderer->beginRenderToBuffer(m_pendingFrame.monitor, fakeDamage, m_pendingFrame.buffer, true)) {\n            LOGM(Log::ERR, \"Can't copy: failed to begin rendering to dmabuf\");\n            return false;\n        }\n\n        render();\n\n        g_pHyprRenderer->endRender([callback = m_pendingFrame.callback]() {\n            if (callback)\n                callback(RESULT_COPIED);\n        });\n    } else if (auto attrs = m_pendingFrame.buffer->shm(); attrs.success) {\n        const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(m_format);\n\n        if (attrs.format != m_format || !PFORMAT) {\n            LOGM(Log::ERR, \"Can't copy: invalid format\");\n            return false;\n        }\n\n        auto outFB = g_pHyprRenderer->createFB();\n        outFB->alloc(m_bufferSize.x, m_bufferSize.y, m_format);\n\n        if (!g_pHyprRenderer->beginFullFakeRender(m_pendingFrame.monitor, fakeDamage, outFB)) {\n            LOGM(Log::ERR, \"Can't copy: failed to begin rendering to shm\");\n            return false;\n        }\n\n        render();\n\n        g_pHyprRenderer->endRender();\n\n        int glFormat = PFORMAT->glFormat;\n\n        if (glFormat == GL_RGBA)\n            glFormat = GL_BGRA_EXT;\n\n        if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) {\n            if (PFORMAT->swizzle.has_value()) {\n                std::array<GLint, 4> RGBA = SWIZZLE_RGBA;\n                std::array<GLint, 4> BGRA = SWIZZLE_BGRA;\n                if (PFORMAT->swizzle == RGBA)\n                    glFormat = GL_RGBA;\n                else if (PFORMAT->swizzle == BGRA)\n                    glFormat = GL_BGRA_EXT;\n                else {\n                    LOGM(Log::ERR, \"Copied frame via shm might be broken or color flipped\");\n                    glFormat = GL_RGBA;\n                }\n            }\n        }\n\n        outFB->readPixels(m_pendingFrame.buffer, 0, 0, m_bufferSize.x, m_bufferSize.y);\n\n        g_pHyprRenderer->m_renderData.pMonitor.reset();\n\n        m_pendingFrame.callback(RESULT_COPIED);\n    } else {\n        LOGM(Log::ERR, \"Can't copy: invalid buffer type\");\n        return false;\n    }\n\n    m_pendingFrame.pending = false;\n    m_constraintsChanged   = false;\n    return true;\n}\n\nDRMFormat CCursorshareSession::format() const {\n    return m_format;\n}\n\nVector2D CCursorshareSession::bufferSize() const {\n    return m_bufferSize;\n}\n\nVector2D CCursorshareSession::hotspot() const {\n    return m_hotspot;\n}\n"
  },
  {
    "path": "src/managers/screenshare/ScreenshareFrame.cpp",
    "content": "#include \"ScreenshareManager.hpp\"\n#include \"../PointerManager.hpp\"\n#include \"../input/InputManager.hpp\"\n#include \"../permissions/DynamicPermissionManager.hpp\"\n#include \"../../protocols/ColorManagement.hpp\"\n#include \"../../protocols/XDGShell.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../render/OpenGL.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"render/pass/RectPassElement.hpp\"\n#include <hyprutils/math/Region.hpp>\n\nusing namespace Screenshare;\n\nCScreenshareFrame::CScreenshareFrame(WP<CScreenshareSession> session, bool overlayCursor, bool isFirst) :\n    m_session(session), m_bufferSize(m_session->bufferSize()), m_overlayCursor(overlayCursor), m_isFirst(isFirst) {\n    ;\n}\n\nCScreenshareFrame::~CScreenshareFrame() {\n    if (m_failed || !m_shared)\n        return;\n\n    if (!m_copied && m_callback)\n        m_callback(RESULT_NOT_COPIED);\n}\n\nbool CScreenshareFrame::done() const {\n    if (m_session.expired() || m_session->m_stopped)\n        return true;\n\n    if (m_session->m_type == SHARE_NONE || m_bufferSize == Vector2D(0, 0))\n        return true;\n\n    if (m_failed || m_copied)\n        return true;\n\n    if (m_session->m_type == SHARE_MONITOR && !m_session->monitor())\n        return true;\n\n    if (m_session->m_type == SHARE_REGION && !m_session->monitor())\n        return true;\n\n    if (m_session->m_type == SHARE_WINDOW && (!m_session->monitor() || !validMapped(m_session->m_window)))\n        return true;\n\n    if (!m_shared)\n        return false;\n\n    if (!m_buffer || !m_buffer->m_resource || !m_buffer->m_resource->good())\n        return true;\n\n    if (!m_callback)\n        return true;\n\n    return false;\n}\n\neScreenshareError CScreenshareFrame::share(SP<IHLBuffer> buffer, const CRegion& clientDamage, FScreenshareCallback callback) {\n    if UNLIKELY (done())\n        return ERROR_STOPPED;\n\n    if UNLIKELY (!m_session->monitor() || !g_pCompositor->monitorExists(m_session->monitor())) {\n        LOGM(Log::ERR, \"Client requested sharing of a monitor that is gone\");\n        m_failed = true;\n        return ERROR_STOPPED;\n    }\n\n    if UNLIKELY (m_session->m_type == SHARE_WINDOW && !validMapped(m_session->m_window)) {\n        LOGM(Log::ERR, \"Client requested sharing of window that is gone or not shareable!\");\n        m_failed = true;\n        return ERROR_STOPPED;\n    }\n\n    if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) {\n        LOGM(Log::ERR, \"Client requested sharing to an invalid buffer\");\n        return ERROR_NO_BUFFER;\n    }\n\n    if UNLIKELY (buffer->size != m_bufferSize) {\n        LOGM(Log::ERR, \"Client requested sharing to an invalid buffer size\");\n        return ERROR_BUFFER_SIZE;\n    }\n\n    uint32_t bufFormat;\n    if (buffer->dmabuf().success)\n        bufFormat = buffer->dmabuf().format;\n    else if (buffer->shm().success)\n        bufFormat = buffer->shm().format;\n    else {\n        LOGM(Log::ERR, \"Client requested sharing to an invalid buffer\");\n        return ERROR_NO_BUFFER;\n    }\n\n    if (std::ranges::count_if(m_session->allowedFormats(), [&](const DRMFormat& format) { return format == bufFormat; }) == 0) {\n        LOGM(Log::ERR, \"Invalid format {} in {:x}\", bufFormat, (uintptr_t)this);\n        return ERROR_BUFFER_FORMAT;\n    }\n\n    m_buffer   = buffer;\n    m_callback = callback;\n    m_shared   = true;\n\n    // schedule a frame so that when a screenshare starts it isn't black until the output is updated\n    if (m_isFirst) {\n        g_pCompositor->scheduleFrameForMonitor(m_session->monitor(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);\n        g_pHyprRenderer->damageMonitor(m_session->monitor());\n    }\n\n    // TODO: add a damage ring for output damage since last shared frame\n    CRegion frameDamage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y);\n\n    // copy everything on the first frame\n    if (m_isFirst)\n        m_damage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y);\n    else\n        m_damage = frameDamage.add(clientDamage);\n\n    m_damage.intersect(0, 0, m_bufferSize.x, m_bufferSize.y);\n\n    return ERROR_NONE;\n}\n\nvoid CScreenshareFrame::copy() {\n    if (done())\n        return;\n\n    // tell client to send presented timestamp\n    // TODO: is this right? this is right after we commit to aq, not when page flip happens..\n    m_callback(RESULT_TIMESTAMP);\n\n    // store a snapshot before the permission popup so we don't break screenshots\n    const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY);\n    if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n        if (!m_session->m_tempFB || !m_session->m_tempFB->isAllocated())\n            storeTempFB();\n\n        // don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty\n        return;\n    }\n\n    if (m_buffer->shm().success)\n        m_failed = !copyShm();\n    else if (m_buffer->dmabuf().success)\n        m_failed = !copyDmabuf();\n\n    if (!m_failed) {\n        // screensharing has started again\n        m_session->screenshareEvents(true);\n        m_session->m_shareStopTimer->updateTimeout(std::chrono::milliseconds(500)); // check in half second\n    } else\n        m_callback(RESULT_NOT_COPIED);\n}\n\nvoid CScreenshareFrame::renderMonitor() {\n    if ((m_session->m_type != SHARE_MONITOR && m_session->m_type != SHARE_REGION) || done())\n        return;\n\n    const auto PMONITOR = m_session->monitor();\n\n    auto       TEXTURE = g_pHyprRenderer->createTexture(PMONITOR->m_output->state->state().buffer);\n\n    const bool IS_CM_AWARE                        = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client);\n    g_pHyprRenderer->m_renderData.transformDamage = false;\n    g_pHyprRenderer->m_renderData.noSimplify      = true;\n\n    // render monitor texture\n    CBox       monbox = CBox{{}, PMONITOR->m_pixelSize}\n                            .transform(Math::wlTransformToHyprutils(Math::invertTransform(PMONITOR->m_transform)), PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y)\n                            .translate(-m_session->m_captureBox.pos()); // vvvv kinda ass-backwards but that's how I designed the renderer... sigh.\n\n    const auto OLD                                    = g_pHyprRenderer->m_renderData.renderModif.enabled;\n    g_pHyprRenderer->m_renderData.renderModif.enabled = false;\n    g_pHyprRenderer->startRenderPass();\n    g_pHyprRenderer->draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                              .tex                = TEXTURE,\n                              .box                = monbox,\n                              .flipEndFrame       = true,\n                              .cmBackToSRGB       = !IS_CM_AWARE,\n                              .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr,\n                          }),\n                          monbox);\n    g_pHyprRenderer->m_renderData.renderModif.enabled = OLD;\n\n    // render black boxes for noscreenshare\n    auto hidePopups = [&](Vector2D popupBaseOffset) {\n        return [&, popupBaseOffset](WP<Desktop::View::CPopup> popup, void*) {\n            if (!popup->wlSurface() || !popup->wlSurface()->resource() || !popup->visible())\n                return;\n\n            const auto popRel = popup->coordsRelativeToParent();\n            popup->wlSurface()->resource()->breadthfirst(\n                [&](SP<CWLSurfaceResource> surf, const Vector2D& localOff, void*) {\n                    const auto size = surf->m_current.size;\n                    const auto surfBox =\n                        CBox{popupBaseOffset + popRel + localOff, size}.translate(PMONITOR->m_position).scale(PMONITOR->m_scale).translate(-m_session->m_captureBox.pos());\n\n                    if LIKELY (surfBox.w > 0 && surfBox.h > 0)\n                        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{\n                                                  .box   = surfBox,\n                                                  .color = Colors::BLACK,\n                                              }),\n                                              surfBox);\n                },\n                nullptr);\n        };\n    };\n\n    for (auto const& l : g_pCompositor->m_layers) {\n        if (!l->m_ruleApplicator->noScreenShare().valueOrDefault())\n            continue;\n\n        if UNLIKELY (!l->visible())\n            continue;\n\n        const auto REALPOS  = l->m_realPosition->value();\n        const auto REALSIZE = l->m_realSize->value();\n\n        const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(REALSIZE.x, 5.0), std::max(REALSIZE.y, 5.0)}\n                                          .translate(-PMONITOR->m_position)\n                                          .scale(PMONITOR->m_scale)\n                                          .translate(-m_session->m_captureBox.pos());\n\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{\n                                  .box   = noScreenShareBox,\n                                  .color = Colors::BLACK,\n                              }),\n                              noScreenShareBox);\n\n        const auto     geom            = l->m_geometry;\n        const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y};\n        if (l->m_popupHead)\n            l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr);\n    }\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!w->m_ruleApplicator->noScreenShare().valueOrDefault())\n            continue;\n\n        if (!g_pHyprRenderer->shouldRenderWindow(w, PMONITOR))\n            continue;\n\n        if (w->isHidden())\n            continue;\n\n        const auto PWORKSPACE = w->m_workspace;\n\n        if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->m_alpha->value() != 0.f)\n            continue;\n\n        const auto renderOffset     = PWORKSPACE && !w->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D{};\n        const auto REALPOS          = w->m_realPosition->value() + renderOffset;\n        const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(w->m_realSize->value().x, 5.0), std::max(w->m_realSize->value().y, 5.0)}\n                                          .translate(-PMONITOR->m_position)\n                                          .scale(PMONITOR->m_scale)\n                                          .translate(-m_session->m_captureBox.pos());\n\n        // seems like rounding doesn't play well with how we manipulate the box position to render regions causing the window to leak through\n        const auto dontRound     = m_session->m_captureBox.pos() != Vector2D() || w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);\n        const auto rounding      = dontRound ? 0 : w->rounding() * PMONITOR->m_scale;\n        const auto roundingPower = dontRound ? 2.0f : w->roundingPower();\n\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{\n                                  .box           = noScreenShareBox,\n                                  .color         = Colors::BLACK,\n                                  .round         = rounding,\n                                  .roundingPower = roundingPower,\n                              }),\n                              noScreenShareBox);\n\n        if (w->m_isX11 || !w->m_popupHead)\n            continue;\n\n        const auto     geom            = w->m_xdgSurface->m_current.geometry;\n        const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y};\n\n        w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr);\n    }\n\n    if (m_overlayCursor) {\n        CRegion  fakeDamage = {0, 0, INT16_MAX, INT16_MAX};\n        Vector2D cursorPos  = g_pInputManager->getMouseCoordsInternal() - PMONITOR->m_position - m_session->m_captureBox.pos() / PMONITOR->m_scale;\n        g_pPointerManager->renderSoftwareCursorsFor(PMONITOR, Time::steadyNow(), fakeDamage, cursorPos, true);\n    }\n}\n\nvoid CScreenshareFrame::renderWindow() {\n    if (m_session->m_type != SHARE_WINDOW || done())\n        return;\n\n    const auto PWINDOW  = m_session->m_window.lock();\n    const auto PMONITOR = m_session->monitor();\n\n    const auto NOW = Time::steadyNow();\n\n    // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that\n    g_pHyprRenderer->m_renderData.fbSize = m_bufferSize;\n    g_pHyprRenderer->setProjectionType(RPT_EXPORT);\n    g_pHyprRenderer->m_renderData.transformDamage = false;\n    g_pHyprRenderer->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y);\n\n    g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(PWINDOW); // block the feedback to avoid spamming the surface if it's visible\n    g_pHyprRenderer->renderWindow(PWINDOW, PMONITOR, NOW, false, RENDER_PASS_ALL, true, true);\n    g_pHyprRenderer->m_bBlockSurfaceFeedback = false;\n\n    if (!m_overlayCursor)\n        return;\n\n    auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock();\n\n    if (!pointerSurfaceResource)\n        return;\n\n    auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource);\n    if (!pointerSurface)\n        return;\n\n    auto box = pointerSurface->getSurfaceBoxGlobal();\n    if (!box.has_value() || box->intersection(m_session->m_window->getFullWindowBoundingBox()).empty())\n        return;\n\n    if (Desktop::focusState()->window() != m_session->m_window)\n        return;\n\n    CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};\n    g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), NOW, fakeDamage, g_pInputManager->getMouseCoordsInternal() - PWINDOW->m_realPosition->value(), true);\n}\n\nvoid CScreenshareFrame::render() {\n    const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY);\n\n    CRegion    frameRegion = {0, 0, g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.x, g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.y};\n    if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n        g_pHyprRenderer->draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), frameRegion);\n        return;\n    }\n\n    bool windowShareDenied = m_session->m_type == SHARE_WINDOW && m_session->m_window->m_ruleApplicator && m_session->m_window->m_ruleApplicator->noScreenShare().valueOrDefault();\n    g_pHyprRenderer->startRenderPass();\n    if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY || windowShareDenied) {\n        g_pHyprRenderer->draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), frameRegion);\n        CBox texbox = CBox{m_bufferSize / 2.F, g_pHyprRenderer->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprRenderer->m_screencopyDeniedTexture->m_size / 2.F);\n        g_pHyprRenderer->draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                                  .tex = g_pHyprRenderer->m_screencopyDeniedTexture,\n                                  .box = texbox,\n                              }),\n                              texbox);\n        return;\n    }\n\n    if (m_session->m_tempFB && m_session->m_tempFB->isAllocated()) {\n        CBox texbox = {{}, m_bufferSize};\n        g_pHyprRenderer->draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                                  .tex = m_session->m_tempFB->getTexture(),\n                                  .box = texbox,\n                              }),\n                              texbox);\n        m_session->m_tempFB->release();\n        return;\n    }\n\n    switch (m_session->m_type) {\n        case SHARE_REGION: // TODO: could this be better? this is how screencopy works\n        case SHARE_MONITOR: renderMonitor(); break;\n        case SHARE_WINDOW: renderWindow(); break;\n        case SHARE_NONE:\n        default: return;\n    }\n}\n\nbool CScreenshareFrame::copyDmabuf() {\n    if (done())\n        return false;\n\n    if (!g_pHyprRenderer->beginRender(m_session->monitor(), m_damage, RENDER_MODE_TO_BUFFER, m_buffer, nullptr, true)) {\n        LOGM(Log::ERR, \"Can't copy: failed to begin rendering to dma frame\");\n        return false;\n    }\n\n    render();\n\n    g_pHyprRenderer->m_renderData.blockScreenShader = true;\n\n    g_pHyprRenderer->endRender([self = m_self]() {\n        if (!self || self.expired() || self->m_copied)\n            return;\n\n        LOGM(Log::TRACE, \"Copied frame via dma\");\n        self->m_callback(RESULT_COPIED);\n        self->m_copied = true;\n    });\n\n    return true;\n}\n\nbool CScreenshareFrame::copyShm() {\n    if (done())\n        return false;\n\n    auto       shm = m_buffer->shm();\n\n    const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format);\n    if (!PFORMAT) {\n        LOGM(Log::ERR, \"Can't copy: failed to find a pixel format\");\n        return false;\n    }\n\n    const auto PMONITOR = m_session->monitor();\n\n    auto       outFB = g_pHyprRenderer->createFB();\n    outFB->alloc(m_bufferSize.x, m_bufferSize.y, shm.format);\n\n    if (!g_pHyprRenderer->beginFullFakeRender(PMONITOR, m_damage, outFB)) {\n        LOGM(Log::ERR, \"Can't copy: failed to begin rendering\");\n        return false;\n    }\n\n    render();\n\n    g_pHyprRenderer->m_renderData.blockScreenShader = true;\n\n    g_pHyprRenderer->endRender();\n\n    m_damage.forEachRect([&](const auto& rect) {\n        int width  = rect.x2 - rect.x1;\n        int height = rect.y2 - rect.y1;\n        outFB->readPixels(m_buffer, rect.x1, rect.y1, width, height);\n    });\n\n    g_pHyprRenderer->m_renderData.pMonitor.reset();\n\n    if (!m_copied) {\n        LOGM(Log::TRACE, \"Copied frame via shm\");\n        m_callback(RESULT_COPIED);\n    }\n\n    return true;\n}\n\nvoid CScreenshareFrame::storeTempFB() {\n    if (!m_session->m_tempFB)\n        m_session->m_tempFB = g_pHyprRenderer->createFB();\n    m_session->m_tempFB->alloc(m_bufferSize.x, m_bufferSize.y);\n\n    CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};\n\n    if (!g_pHyprRenderer->beginFullFakeRender(m_session->monitor(), fakeDamage, m_session->m_tempFB)) {\n        LOGM(Log::ERR, \"Can't copy: failed to begin rendering to temp fb\");\n        return;\n    }\n\n    switch (m_session->m_type) {\n        case SHARE_REGION: // TODO: could this be better? this is how screencopy works\n        case SHARE_MONITOR: renderMonitor(); break;\n        case SHARE_WINDOW: renderWindow(); break;\n        case SHARE_NONE:\n        default: return;\n    }\n\n    g_pHyprRenderer->endRender();\n}\n\nVector2D CScreenshareFrame::bufferSize() const {\n    return m_bufferSize;\n}\n\nwl_output_transform CScreenshareFrame::transform() const {\n    switch (m_session->m_type) {\n        case SHARE_REGION:\n        case SHARE_MONITOR: return m_session->monitor()->m_transform;\n        default:\n        case SHARE_WINDOW: return WL_OUTPUT_TRANSFORM_NORMAL;\n    }\n}\n\nconst CRegion& CScreenshareFrame::damage() const {\n    return m_damage;\n}\n"
  },
  {
    "path": "src/managers/screenshare/ScreenshareManager.cpp",
    "content": "#include \"ScreenshareManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../protocols/core/Seat.hpp\"\n\nusing namespace Screenshare;\n\nCScreenshareManager::CScreenshareManager() {\n    ;\n}\n\nvoid CScreenshareManager::onOutputCommit(PHLMONITOR monitor) {\n    std::erase_if(m_sessions, [&](const WP<CScreenshareSession>& session) { return session.expired(); });\n\n    // if no pending frames, and no sessions are sharing, then unblock ds\n    if (m_pendingFrames.empty()) {\n        for (const auto& session : m_sessions) {\n            if (!session->m_stopped && session->m_sharing)\n                return;\n        }\n\n        g_pHyprRenderer->m_directScanoutBlocked = false;\n        return; // nothing to share\n    }\n\n    std::ranges::for_each(m_pendingFrames, [&](WP<CScreenshareFrame>& frame) {\n        if (frame.expired() || !frame->m_shared || frame->done())\n            return;\n\n        if (frame->m_session->monitor() != monitor)\n            return;\n\n        if (frame->m_session->m_type == SHARE_WINDOW) {\n            CBox geometry = {frame->m_session->m_window->m_realPosition->value(), frame->m_session->m_window->m_realSize->value()};\n            if (geometry.intersection({monitor->m_position, monitor->m_size}).empty())\n                return;\n        }\n\n        frame->copy();\n    });\n\n    std::erase_if(m_pendingFrames, [&](const WP<CScreenshareFrame>& frame) { return frame.expired(); });\n}\n\nUP<CScreenshareSession> CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor) {\n    if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) {\n        LOGM(Log::ERR, \"Client requested sharing of a monitor that is gone\");\n        return nullptr;\n    }\n\n    UP<CScreenshareSession> session = UP<CScreenshareSession>(new CScreenshareSession(monitor, client));\n\n    session->m_self = session;\n    m_sessions.emplace_back(session);\n\n    return session;\n}\n\nUP<CScreenshareSession> CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion) {\n    if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) {\n        LOGM(Log::ERR, \"Client requested sharing of a monitor that is gone\");\n        return nullptr;\n    }\n\n    UP<CScreenshareSession> session = UP<CScreenshareSession>(new CScreenshareSession(monitor, captureRegion, client));\n\n    session->m_self = session;\n    m_sessions.emplace_back(session);\n\n    return session;\n}\n\nUP<CScreenshareSession> CScreenshareManager::newSession(wl_client* client, PHLWINDOW window) {\n    if UNLIKELY (!window || !window->m_isMapped) {\n        LOGM(Log::ERR, \"Client requested sharing of window that is gone or not shareable!\");\n        return nullptr;\n    }\n\n    UP<CScreenshareSession> session = UP<CScreenshareSession>(new CScreenshareSession(window, client));\n\n    session->m_self = session;\n    m_sessions.emplace_back(session);\n\n    return session;\n}\n\nUP<CCursorshareSession> CScreenshareManager::newCursorSession(wl_client* client, WP<CWLPointerResource> pointer) {\n    UP<CCursorshareSession> session = UP<CCursorshareSession>(new CCursorshareSession(client, pointer));\n\n    session->m_self = session;\n    m_cursorSessions.emplace_back(session);\n\n    return session;\n}\n\nWP<CScreenshareSession> CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor) {\n    return getManagedSession(SHARE_MONITOR, client, monitor, nullptr, {});\n}\n\nWP<CScreenshareSession> CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox) {\n\n    return getManagedSession(SHARE_REGION, client, monitor, nullptr, captureBox);\n}\n\nWP<CScreenshareSession> CScreenshareManager::getManagedSession(wl_client* client, PHLWINDOW window) {\n    return getManagedSession(SHARE_WINDOW, client, nullptr, window, {});\n}\n\nWP<CScreenshareSession> CScreenshareManager::getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox) {\n    if (type == SHARE_NONE)\n        return {};\n\n    auto it = std::ranges::find_if(m_managedSessions, [&](const auto& session) {\n        if (session->m_session->m_client != client || session->m_session->m_type != type)\n            return false;\n\n        switch (type) {\n            case SHARE_MONITOR: return session->m_session->m_monitor == monitor;\n            case SHARE_WINDOW: return session->m_session->m_window == window;\n            case SHARE_REGION: return session->m_session->m_monitor == monitor && session->m_session->m_captureBox == captureBox;\n            case SHARE_NONE:\n            default: return false;\n        }\n\n        return false;\n    });\n\n    if (it == m_managedSessions.end()) {\n        UP<CScreenshareSession> session;\n        switch (type) {\n            case SHARE_MONITOR: session = UP<CScreenshareSession>(new CScreenshareSession(monitor, client)); break;\n            case SHARE_WINDOW: session = UP<CScreenshareSession>(new CScreenshareSession(window, client)); break;\n            case SHARE_REGION: session = UP<CScreenshareSession>(new CScreenshareSession(monitor, captureBox, client)); break;\n            case SHARE_NONE:\n            default: return {};\n        }\n\n        session->m_self = session;\n        m_sessions.emplace_back(session);\n\n        it = m_managedSessions.emplace(m_managedSessions.end(), makeUnique<SManagedSession>(std::move(session)));\n    }\n\n    auto& session = *it;\n\n    session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP<SManagedSession>(session)]() {\n        if (!session.expired())\n            std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); });\n    });\n\n    return session->m_session;\n}\n\nbool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) {\n    return std::ranges::any_of(m_sessions, [monitor](const auto& s) {\n        if (!s)\n            return false;\n        return (s->m_type == SHARE_MONITOR || s->m_type == SHARE_REGION) && s->m_monitor == monitor;\n    });\n}\n\nCScreenshareManager::SManagedSession::SManagedSession(UP<CScreenshareSession>&& session) : m_session(std::move(session)) {\n    ;\n}\n"
  },
  {
    "path": "src/managers/screenshare/ScreenshareManager.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../protocols/types/Buffer.hpp\"\n#include \"../../render/Framebuffer.hpp\"\n#include \"../eventLoop/EventLoopTimer.hpp\"\n#include \"../../render/Renderer.hpp\"\n\n// TODO: do screenshare damage\n\nclass CWLPointerResource;\n\nnamespace Screenshare {\n    enum eScreenshareType : uint8_t {\n        SHARE_MONITOR,\n        SHARE_WINDOW,\n        SHARE_REGION,\n        SHARE_NONE\n    };\n\n    enum eScreenshareError : uint8_t {\n        ERROR_NONE,\n        ERROR_UNKNOWN,\n        ERROR_STOPPED,\n        ERROR_NO_BUFFER,\n        ERROR_BUFFER_SIZE,\n        ERROR_BUFFER_FORMAT\n    };\n\n    enum eScreenshareResult : uint8_t {\n        RESULT_COPIED,\n        RESULT_NOT_COPIED,\n        RESULT_TIMESTAMP,\n    };\n\n    using FScreenshareCallback = std::function<void(eScreenshareResult result)>;\n    using FSourceBoxCallback   = std::function<CBox(void)>;\n\n    class CScreenshareSession {\n      public:\n        CScreenshareSession(const CScreenshareSession&) = delete;\n        CScreenshareSession(CScreenshareSession&&)      = delete;\n        ~CScreenshareSession();\n\n        UP<CScreenshareFrame> nextFrame(bool overlayCursor);\n        void                  stop();\n\n        // constraints\n        const std::vector<DRMFormat>& allowedFormats() const;\n        Vector2D                      bufferSize() const;\n        PHLMONITOR                    monitor() const; // this will return the correct monitor based on type\n\n        struct {\n            CSignalT<> stopped;\n            CSignalT<> constraintsChanged;\n        } m_events;\n\n      private:\n        CScreenshareSession(PHLMONITOR monitor, wl_client* client);\n        CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client);\n        CScreenshareSession(PHLWINDOW window, wl_client* client);\n\n        WP<CScreenshareSession> m_self;\n        bool                    m_stopped = false;\n\n        eScreenshareType        m_type = SHARE_NONE;\n        PHLMONITORREF           m_monitor;\n        PHLWINDOWREF            m_window;\n        CBox                    m_captureBox = {}; // given capture area in logical coordinates (see xdg_output)\n\n        wl_client*              m_client = nullptr;\n        std::string             m_name   = \"\";\n\n        std::vector<DRMFormat>  m_formats;\n        Vector2D                m_bufferSize = Vector2D(0, 0);\n\n        SP<IFramebuffer>        m_tempFB;\n\n        SP<CEventLoopTimer>     m_shareStopTimer;\n        bool                    m_sharing = false;\n\n        struct {\n            CHyprSignalListener monitorDestroyed;\n            CHyprSignalListener monitorModeChanged;\n            CHyprSignalListener windowDestroyed;\n            CHyprSignalListener windowSizeChanged;\n            CHyprSignalListener windowMonitorChanged;\n        } m_listeners;\n\n        void screenshareEvents(bool started);\n        void calculateConstraints();\n        void init();\n\n        friend class CScreenshareFrame;\n        friend class CScreenshareManager;\n    };\n\n    class CCursorshareSession {\n      public:\n        CCursorshareSession(const CCursorshareSession&) = delete;\n        CCursorshareSession(CCursorshareSession&&)      = delete;\n        ~CCursorshareSession();\n\n        eScreenshareError share(PHLMONITOR monitor, SP<IHLBuffer> buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback);\n        void              stop();\n\n        // constraints\n        DRMFormat format() const;\n        Vector2D  bufferSize() const;\n        Vector2D  hotspot() const;\n\n        struct {\n            CSignalT<> stopped;\n            CSignalT<> constraintsChanged;\n        } m_events;\n\n      private:\n        CCursorshareSession(wl_client* client, WP<CWLPointerResource> pointer);\n\n        WP<CCursorshareSession> m_self;\n        bool                    m_stopped            = false;\n        bool                    m_constraintsChanged = true;\n\n        wl_client*              m_client = nullptr;\n        WP<CWLPointerResource>  m_pointer;\n\n        // constraints\n        DRMFormat m_format     = 0 /* DRM_FORMAT_INVALID */;\n        Vector2D  m_hotspot    = Vector2D(0, 0);\n        Vector2D  m_bufferSize = Vector2D(0, 0);\n\n        struct {\n            bool                 pending = false;\n            PHLMONITOR           monitor;\n            SP<IHLBuffer>        buffer;\n            FSourceBoxCallback   sourceBoxCallback;\n            FScreenshareCallback callback;\n        } m_pendingFrame;\n\n        struct {\n            CHyprSignalListener pointerDestroyed;\n            CHyprSignalListener cursorChanged;\n        } m_listeners;\n\n        bool copy();\n        void render();\n        void calculateConstraints();\n\n        friend class CScreenshareFrame;\n        friend class CScreenshareManager;\n    };\n\n    class CScreenshareFrame {\n      public:\n        CScreenshareFrame(const CScreenshareFrame&) = delete;\n        CScreenshareFrame(CScreenshareFrame&&)      = delete;\n        CScreenshareFrame(WP<CScreenshareSession> session, bool overlayCursor, bool isFirst);\n        ~CScreenshareFrame();\n\n        bool                done() const;\n        eScreenshareError   share(SP<IHLBuffer> buffer, const CRegion& damage, FScreenshareCallback callback);\n\n        Vector2D            bufferSize() const;\n        wl_output_transform transform() const; // returns the transform applied by compositor on the buffer\n        const CRegion&      damage() const;\n\n      private:\n        WP<CScreenshareFrame>   m_self;\n        WP<CScreenshareSession> m_session;\n        FScreenshareCallback    m_callback;\n        SP<IHLBuffer>           m_buffer;\n        Vector2D                m_bufferSize = Vector2D(0, 0);\n        CRegion                 m_damage; // damage in buffer coords\n        bool                    m_shared = false, m_copied = false, m_failed = false;\n        bool                    m_overlayCursor = true;\n        bool                    m_isFirst       = false;\n\n        //\n        void copy();\n        bool copyDmabuf();\n        bool copyShm();\n\n        void render();\n        void renderMonitor();\n        void renderMonitorRegion();\n        void renderWindow();\n\n        void storeTempFB();\n\n        friend class CScreenshareManager;\n        friend class CScreenshareSession;\n    };\n\n    class CScreenshareManager {\n      public:\n        CScreenshareManager();\n\n        UP<CScreenshareSession> newSession(wl_client* client, PHLMONITOR monitor);\n        UP<CScreenshareSession> newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion);\n        UP<CScreenshareSession> newSession(wl_client* client, PHLWINDOW window);\n\n        WP<CScreenshareSession> getManagedSession(wl_client* client, PHLMONITOR monitor);\n        WP<CScreenshareSession> getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox);\n        WP<CScreenshareSession> getManagedSession(wl_client* client, PHLWINDOW window);\n\n        UP<CCursorshareSession> newCursorSession(wl_client* client, WP<CWLPointerResource> pointer);\n\n        void                    onOutputCommit(PHLMONITOR monitor);\n        bool                    isOutputBeingSSd(PHLMONITOR monitor);\n\n      private:\n        std::vector<WP<CScreenshareSession>> m_sessions;\n        std::vector<WP<CCursorshareSession>> m_cursorSessions;\n        std::vector<WP<CScreenshareFrame>>   m_pendingFrames;\n\n        struct SManagedSession {\n            SManagedSession(UP<CScreenshareSession>&& session);\n\n            UP<CScreenshareSession> m_session;\n            CHyprSignalListener     stoppedListener;\n        };\n\n        std::vector<UP<SManagedSession>> m_managedSessions;\n        WP<CScreenshareSession>          getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox);\n\n        friend class CScreenshareSession;\n    };\n\n    inline UP<CScreenshareManager>& mgr() {\n        static UP<CScreenshareManager> manager = nullptr;\n        if (!manager && g_pHyprRenderer) {\n            Log::logger->log(Log::DEBUG, \"Starting ScreenshareManager\");\n            manager = makeUnique<CScreenshareManager>();\n        }\n        return manager;\n    }\n}\n\ntemplate <>\nstruct std::formatter<Screenshare::eScreenshareType> : std::formatter<std::string> {\n    auto format(const Screenshare::eScreenshareType& res, std::format_context& ctx) const {\n        switch (res) {\n            case Screenshare::SHARE_MONITOR: return formatter<string>::format(\"monitor\", ctx);\n            case Screenshare::SHARE_WINDOW: return formatter<string>::format(\"window\", ctx);\n            case Screenshare::SHARE_REGION: return formatter<string>::format(\"region\", ctx);\n            case Screenshare::SHARE_NONE: return formatter<string>::format(\"ERR NONE\", ctx);\n        }\n        return formatter<string>::format(\"error\", ctx);\n    }\n};\n"
  },
  {
    "path": "src/managers/screenshare/ScreenshareSession.cpp",
    "content": "#include \"ScreenshareManager.hpp\"\n#include \"../../render/OpenGL.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../EventManager.hpp\"\n#include \"../eventLoop/EventLoopManager.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nusing namespace Screenshare;\n\nCScreenshareSession::CScreenshareSession(PHLMONITOR monitor, wl_client* client) : m_type(SHARE_MONITOR), m_monitor(monitor), m_client(client) {\n    if UNLIKELY (!m_monitor)\n        return;\n\n    init();\n}\n\nCScreenshareSession::CScreenshareSession(PHLWINDOW window, wl_client* client) : m_type(SHARE_WINDOW), m_window(window), m_client(client) {\n    if UNLIKELY (!m_window)\n        return;\n\n    m_listeners.windowDestroyed      = m_window->m_events.unmap.listen([this]() { stop(); });\n    m_listeners.windowSizeChanged    = m_window->m_events.resize.listen([this]() {\n        calculateConstraints();\n        m_events.constraintsChanged.emit();\n    });\n    m_listeners.windowMonitorChanged = m_window->m_events.monitorChanged.listen([this]() {\n        m_listeners.monitorDestroyed   = monitor()->m_events.disconnect.listen([this]() { stop(); });\n        m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() {\n            calculateConstraints();\n            m_events.constraintsChanged.emit();\n        });\n\n        calculateConstraints();\n        m_events.constraintsChanged.emit();\n    });\n\n    init();\n}\n\nCScreenshareSession::CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client) :\n    m_type(SHARE_REGION), m_monitor(monitor), m_captureBox(captureRegion), m_client(client) {\n    if UNLIKELY (!m_monitor)\n        return;\n\n    init();\n}\n\nCScreenshareSession::~CScreenshareSession() {\n    stop();\n    uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get());\n    LOGM(Log::TRACE, \"Destroyed screenshare session for ({}): {}, {:x}\", m_type, m_name, ptr);\n}\n\nvoid CScreenshareSession::stop() {\n    if (m_stopped)\n        return;\n    m_stopped = true;\n    m_events.stopped.emit();\n\n    screenshareEvents(false);\n}\n\nvoid CScreenshareSession::init() {\n    uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get());\n    LOGM(Log::TRACE, \"Created screenshare session for ({}): {}, {:x}\", m_type, m_name, ptr);\n\n    m_shareStopTimer = makeShared<CEventLoopTimer>(\n        std::chrono::milliseconds(500),\n        [this](SP<CEventLoopTimer> self, void* data) {\n            // if this fires, then it's been half a second since the last frame, so we aren't sharing\n            screenshareEvents(false);\n        },\n        nullptr);\n\n    if (g_pEventLoopManager)\n        g_pEventLoopManager->addTimer(m_shareStopTimer);\n\n    // scale capture box since it's in logical coords\n    m_captureBox.scale(monitor()->m_scale);\n\n    m_listeners.monitorDestroyed   = monitor()->m_events.disconnect.listen([this]() { stop(); });\n    m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() {\n        calculateConstraints();\n        m_events.constraintsChanged.emit();\n    });\n\n    calculateConstraints();\n}\n\nvoid CScreenshareSession::calculateConstraints() {\n    const auto PMONITOR = monitor();\n    if (!PMONITOR) {\n        stop();\n        return;\n    }\n\n    // TODO: maybe support more that just monitor format in the future?\n    m_formats.clear();\n    m_formats.push_back(NFormatUtils::alphaFormat(PMONITOR->getPreferredReadFormat()));\n    m_formats.push_back(PMONITOR->getPreferredReadFormat()); // some clients don't like alpha formats\n\n    // TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT won't work here\n    for (auto& format : m_formats) {\n        if (format == DRM_FORMAT_XRGB2101010 || format == DRM_FORMAT_ARGB2101010)\n            format = DRM_FORMAT_XBGR2101010;\n    }\n\n    switch (m_type) {\n        case SHARE_MONITOR:\n            m_bufferSize = PMONITOR->m_pixelSize;\n            m_name       = PMONITOR->m_name;\n            break;\n        case SHARE_WINDOW:\n            m_bufferSize = (m_window->m_realSize->value() * PMONITOR->m_scale).round();\n            m_name       = m_window->m_title;\n            break;\n        case SHARE_REGION:\n            m_bufferSize = PMONITOR->m_transform % 2 == 0 ? m_captureBox.size() : Vector2D{m_captureBox.h, m_captureBox.w};\n            m_name       = PMONITOR->m_name;\n            break;\n        case SHARE_NONE:\n        default:\n            LOGM(Log::ERR, \"Invalid share type?? This shouldn't happen\");\n            stop();\n            return;\n    }\n\n    LOGM(Log::TRACE, \"constraints changed for {}\", m_name);\n}\n\nvoid CScreenshareSession::screenshareEvents(bool startSharing) {\n    if (startSharing && !m_sharing) {\n        m_sharing = true;\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"screencast\", .data = std::format(\"1,{}\", m_type)});\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"screencastv2\", .data = std::format(\"1,{},{}\", m_type, m_name)});\n        LOGM(Log::INFO, \"Started screenshare session for ({}): {}\", m_type, m_name);\n\n        Event::bus()->m_events.screenshare.state.emit(true, m_type, m_name);\n    } else if (!startSharing && m_sharing) {\n        m_sharing = false;\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"screencast\", .data = std::format(\"0,{}\", m_type)});\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"screencastv2\", .data = std::format(\"0,{},{}\", m_type, m_name)});\n        LOGM(Log::INFO, \"Stopped screenshare session for ({}): {}\", m_type, m_name);\n\n        Event::bus()->m_events.screenshare.state.emit(false, m_type, m_name);\n    }\n}\n\nconst std::vector<DRMFormat>& CScreenshareSession::allowedFormats() const {\n    return m_formats;\n}\n\nVector2D CScreenshareSession::bufferSize() const {\n    return m_bufferSize;\n}\n\nPHLMONITOR CScreenshareSession::monitor() const {\n    if (m_type == SHARE_WINDOW && m_window.expired())\n        return nullptr;\n    PHLMONITORREF mon = m_type == SHARE_WINDOW ? m_window->m_monitor : m_monitor;\n    return mon.expired() ? nullptr : mon.lock();\n}\n\nUP<CScreenshareFrame> CScreenshareSession::nextFrame(bool overlayCursor) {\n    UP<CScreenshareFrame> frame = makeUnique<CScreenshareFrame>(m_self, overlayCursor, !m_sharing);\n    frame->m_self               = frame;\n\n    Screenshare::mgr()->m_pendingFrames.emplace_back(frame);\n\n    // there is now a pending frame, so block ds\n    g_pHyprRenderer->m_directScanoutBlocked = true;\n\n    return frame;\n}\n"
  },
  {
    "path": "src/pch/pch.hpp",
    "content": "#include <algorithm>\n#include <any>\n#include <array>\n#include <chrono>\n#include <concepts>\n#include <filesystem>\n#include <fstream>\n#include <functional>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n#include <list>\n#include <map>\n\n#include <mutex>\n#include <optional>\n#include <random>\n#include <ranges>\n#include <set>\n#include <sstream>\n#include <string>\n#include <thread>\n#include <tuple>\n#include <unordered_map>\n#include <vector>\n#include <format>"
  },
  {
    "path": "src/plugins/HookSystem.cpp",
    "content": "#include \"HookSystem.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../helpers/varlist/VarList.hpp\"\n#include \"../managers/TokenManager.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\n\n#define register\n#include <udis86.h>\n#undef register\n#include <sys/mman.h>\n#include <unistd.h>\n#include <cstring>\n#include <fstream>\n#include <sys/stat.h>\n#include <sys/types.h>\n\nCFunctionHook::CFunctionHook(HANDLE owner, void* source, void* destination) : m_source(source), m_destination(destination), m_owner(owner) {\n    ;\n}\n\nCFunctionHook::~CFunctionHook() {\n    if (m_active)\n        unhook();\n}\n\nCFunctionHook::SInstructionProbe CFunctionHook::getInstructionLenAt(void* start) {\n    ud_t udis;\n\n    ud_init(&udis);\n    ud_set_mode(&udis, 64);\n    ud_set_syntax(&udis, UD_SYN_ATT);\n\n    size_t curOffset = 1;\n    size_t insSize   = 0;\n    while (true) {\n        ud_set_input_buffer(&udis, sc<uint8_t*>(start), curOffset);\n        insSize = ud_disassemble(&udis);\n        if (insSize != curOffset)\n            break;\n        curOffset++;\n    }\n\n    // check for RIP refs\n    std::string ins;\n    if (const auto CINS = ud_insn_asm(&udis); CINS)\n        ins = std::string(CINS);\n\n    return {insSize, ins};\n}\n\nCFunctionHook::SInstructionProbe CFunctionHook::probeMinimumJumpSize(void* start, size_t min) {\n\n    size_t              size = 0;\n\n    std::string         instrs = \"\";\n    std::vector<size_t> sizes;\n\n    while (size <= min) {\n        // find info about this instruction\n        auto probe = getInstructionLenAt(sc<uint8_t*>(start) + size);\n        sizes.push_back(probe.len);\n        size += probe.len;\n        instrs += probe.assembly + \"\\n\";\n    }\n\n    return {size, instrs, sizes};\n}\n\nCFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstructionProbe& probe) {\n    SAssembly returns;\n\n    // analyze the code and fix what we know how to.\n    uint64_t currentAddress = rc<uint64_t>(m_source);\n    // actually newline + 1\n    size_t lastAsmNewline = 0;\n    // needle for destination binary\n    size_t            currentDestinationOffset = 0;\n\n    std::vector<char> finalBytes;\n    finalBytes.resize(probe.len);\n\n    for (auto const& len : probe.insSizes) {\n\n        // copy original bytes to our finalBytes\n        for (size_t i = 0; i < len; ++i) {\n            finalBytes[currentDestinationOffset + i] = *rc<char*>(currentAddress + i);\n        }\n\n        std::string code = probe.assembly.substr(lastAsmNewline, probe.assembly.find('\\n', lastAsmNewline) - lastAsmNewline);\n        if (code.contains(\"%rip\")) {\n            CVarList    tokens{code, 0, 's'};\n            size_t      plusPresent  = tokens[1][0] == '+' ? 1 : 0;\n            size_t      minusPresent = tokens[1][0] == '-' ? 1 : 0;\n            std::string addr         = tokens[1].substr((plusPresent || minusPresent), tokens[1].find(\"(%rip)\") - (plusPresent || minusPresent));\n            auto        addrResult   = configStringToInt(addr);\n            if (!addrResult)\n                return {};\n            const int32_t OFFSET = (minusPresent ? -1 : 1) * *addrResult;\n            if (OFFSET == 0)\n                return {};\n            const uint64_t DESTINATION = currentAddress + OFFSET + len;\n\n            auto           ADDREND   = code.find(\"(%rip)\");\n            auto           ADDRSTART = (code.substr(0, ADDREND).find_last_of(' '));\n\n            if (ADDREND == std::string::npos || ADDRSTART == std::string::npos)\n                return {};\n\n            const uint64_t PREDICTEDRIP = rc<uint64_t>(m_landTrampolineAddr) + currentDestinationOffset + len;\n            const int32_t  NEWRIPOFFSET = DESTINATION - PREDICTEDRIP;\n\n            size_t         ripOffset = 0;\n\n            // find %rip usage offset from beginning\n            for (int i = len - 4 /* 32-bit */; i > 0; --i) {\n                if (*rc<int32_t*>(currentAddress + i) == OFFSET) {\n                    ripOffset = i;\n                    break;\n                }\n            }\n\n            if (ripOffset == 0)\n                return {};\n\n            // fix offset in the final bytes. This doesn't care about endianness\n            *rc<int32_t*>(&finalBytes[currentDestinationOffset + ripOffset]) = NEWRIPOFFSET;\n\n            currentDestinationOffset += len;\n        } else {\n            currentDestinationOffset += len;\n        }\n\n        lastAsmNewline = probe.assembly.find('\\n', lastAsmNewline) + 1;\n        currentAddress += len;\n    }\n\n    return {finalBytes};\n}\n\nbool CFunctionHook::hook() {\n\n    // check for unsupported platforms\n#if !defined(__x86_64__)\n    return false;\n#endif\n\n    if (g_pFunctionHookSystem->m_activeHooks.contains(rc<uint64_t>(m_source))) {\n        // TODO: return actual error codes...\n        Log::logger->log(Log::ERR, \"[functionhook] failed, function is already hooked\");\n        return false;\n    }\n\n    // jmp rel32\n    // offset for relative addr: 1\n    static constexpr uint8_t RELATIVE_JMP_ADDRESS[]      = {0xE9, 0x00, 0x00, 0x00, 0x00};\n    static constexpr size_t  RELATIVE_JMP_ADDRESS_OFFSET = 1;\n    // movabs $0,%rax | jmpq *rax\n    static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[]      = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};\n    static constexpr size_t  ABSOLUTE_JMP_ADDRESS_OFFSET = 2;\n    // nop\n    static constexpr uint8_t NOP = 0x90;\n\n    // alloc trampolines\n    const auto MAX_TRAMPOLINE_SIZE = HOOK_TRAMPOLINE_MAX_SIZE; // we will never need more.\n    m_launchTrampolineAddr         = rc<void*>(g_pFunctionHookSystem->getAddressForTrampo());\n    m_landTrampolineAddr           = rc<void*>(g_pFunctionHookSystem->getAddressForTrampo());\n\n    // probe instructions to be trampolin'd\n    SInstructionProbe probe;\n    try {\n        probe = probeMinimumJumpSize(m_source, sizeof(RELATIVE_JMP_ADDRESS));\n    } catch (std::exception& e) { return false; }\n\n    const auto PROBEFIXEDASM = fixInstructionProbeRIPCalls(probe);\n\n    if (PROBEFIXEDASM.bytes.empty()) {\n        Log::logger->log(Log::ERR, \"[functionhook] failed, unsupported asm / failed assembling:\\n{}\", probe.assembly);\n        return false;\n    }\n\n    if (std::abs(rc<int64_t>(m_source) - rc<int64_t>(m_landTrampolineAddr)) > 2000000000 /* 2 GB */) {\n        Log::logger->log(Log::ERR, \"[functionhook] failed, source and trampo are over 2GB apart\");\n        return false;\n    }\n\n    const size_t HOOKSIZE = PROBEFIXEDASM.bytes.size();\n    const size_t ORIGSIZE = probe.len;\n\n    const auto   TRAMPOLINE_SIZE = sizeof(RELATIVE_JMP_ADDRESS) + HOOKSIZE;\n\n    if (TRAMPOLINE_SIZE > MAX_TRAMPOLINE_SIZE) {\n        Log::logger->log(Log::ERR, \"[functionhook] failed, not enough space in trampo to alloc:\\n{}\", probe.assembly);\n        return false;\n    }\n\n    m_originalBytes.resize(ORIGSIZE);\n    memcpy(m_originalBytes.data(), m_source, ORIGSIZE);\n\n    // populate land trampoline\n    memcpy(m_landTrampolineAddr, PROBEFIXEDASM.bytes.data(), HOOKSIZE);                                        // first, original but fixed func bytes\n    memcpy(sc<uint8_t*>(m_landTrampolineAddr) + HOOKSIZE, RELATIVE_JMP_ADDRESS, sizeof(RELATIVE_JMP_ADDRESS)); // then, jump to source\n\n    // populate short jump addr\n    *rc<int32_t*>(sc<uint8_t*>(m_landTrampolineAddr) + TRAMPOLINE_SIZE - sizeof(RELATIVE_JMP_ADDRESS) + RELATIVE_JMP_ADDRESS_OFFSET) =\n        sc<int64_t>((sc<uint8_t*>(m_source) + probe.len)                     // jump to source + probe len (skip header)\n                    - (sc<uint8_t*>(m_landTrampolineAddr) + TRAMPOLINE_SIZE) // from trampo + size - jmp (not - size because jmp is rel to rip after instr)\n        );\n\n    // populate launch trampoline\n    memcpy(m_launchTrampolineAddr, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // long jump to our hk\n\n    // populate long jump addr\n    *rc<uint64_t*>(sc<uint8_t*>(m_launchTrampolineAddr) + ABSOLUTE_JMP_ADDRESS_OFFSET) = rc<uint64_t>(m_destination); // long jump to hk fn\n\n    // make short jump to launch trampoile\n    const auto     PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE);\n    const uint8_t* PROTSTART    = sc<uint8_t*>(m_source) - (rc<uint64_t>(m_source) % PAGESIZE_VAR);\n    const size_t   PROTLEN      = std::ceil(sc<float>(ORIGSIZE + (rc<uint64_t>(m_source) - rc<uint64_t>(PROTSTART))) / sc<float>(PAGESIZE_VAR)) * PAGESIZE_VAR;\n    mprotect(const_cast<uint8_t*>(PROTSTART), PROTLEN, PROT_READ | PROT_WRITE | PROT_EXEC);\n    memcpy(m_source, RELATIVE_JMP_ADDRESS, sizeof(RELATIVE_JMP_ADDRESS));\n\n    size_t currentOp = sizeof(RELATIVE_JMP_ADDRESS);\n    memset(sc<uint8_t*>(m_source) + currentOp, NOP, ORIGSIZE - currentOp);\n\n    // populate short jump addr\n    *rc<int32_t*>(sc<uint8_t*>(m_source) + RELATIVE_JMP_ADDRESS_OFFSET) = sc<int32_t>( //\n        rc<uint64_t>(m_launchTrampolineAddr)                                           // jump to the launch trampoline which jumps to hk\n        - (rc<uint64_t>(m_source) + 5)                                                 // from source\n    );\n\n    // revert mprot\n    mprotect(const_cast<uint8_t*>(PROTSTART), PROTLEN, PROT_READ | PROT_EXEC);\n\n    // set original addr to land trampo addr\n    m_original = m_landTrampolineAddr;\n\n    m_active  = true;\n    m_hookLen = ORIGSIZE;\n\n    g_pFunctionHookSystem->m_activeHooks.emplace(rc<uint64_t>(m_source));\n\n    return true;\n}\n\nbool CFunctionHook::unhook() {\n    // check for unsupported platforms\n#if !defined(__x86_64__)\n    return false;\n#endif\n\n    if (!m_active)\n        return false;\n\n    g_pFunctionHookSystem->m_activeHooks.erase(rc<uint64_t>(m_source));\n\n    // allow write to src\n    mprotect(sc<uint8_t*>(m_source) - rc<uint64_t>(m_source) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC);\n\n    // write back original bytes\n    memcpy(m_source, m_originalBytes.data(), m_hookLen);\n\n    // revert mprot\n    mprotect(sc<uint8_t*>(m_source) - rc<uint64_t>(m_source) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);\n\n    // reset vars\n    m_active               = false;\n    m_hookLen              = 0;\n    m_landTrampolineAddr   = nullptr; // no unmapping, it's managed by the HookSystem\n    m_launchTrampolineAddr = nullptr; // no unmapping, it's managed by the HookSystem\n    m_original             = nullptr;\n    m_originalBytes.clear();\n\n    return true;\n}\n\nCFunctionHook* CHookSystem::initHook(HANDLE owner, void* source, void* destination) {\n    return m_hooks.emplace_back(makeUnique<CFunctionHook>(owner, source, destination)).get();\n}\n\nbool CHookSystem::removeHook(CFunctionHook* hook) {\n    std::erase_if(m_hooks, [&](const auto& other) { return other.get() == hook; });\n    return true; // todo: make false if not found\n}\n\nvoid CHookSystem::removeAllHooksFrom(HANDLE handle) {\n    std::erase_if(m_hooks, [&](const auto& other) { return other->m_owner == handle; });\n}\n\nstatic uintptr_t seekNewPageAddr() {\n    const uint64_t PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE);\n    auto           MAPS         = std::ifstream(\"/proc/self/maps\");\n\n    uint64_t       lastStart = 0, lastEnd = 0;\n\n    bool           anchoredToHyprland = false;\n\n    std::string    line;\n    while (std::getline(MAPS, line)) {\n        CVarList props{line, 0, 's', true};\n\n        uint64_t start = 0, end = 0;\n        if (props[0].empty()) {\n            Log::logger->log(Log::WARN, \"seekNewPageAddr: unexpected line in self maps\");\n            continue;\n        }\n\n        CVarList startEnd{props[0], 0, '-', true};\n\n        try {\n            start = std::stoull(startEnd[0], nullptr, 16);\n            end   = std::stoull(startEnd[1], nullptr, 16);\n        } catch (std::exception& e) {\n            Log::logger->log(Log::WARN, \"seekNewPageAddr: unexpected line in self maps: {}\", line);\n            continue;\n        }\n\n        Log::logger->log(Log::DEBUG, \"seekNewPageAddr: page 0x{:x} - 0x{:x}\", start, end);\n\n        if (lastStart == 0) {\n            lastStart = start;\n            lastEnd   = end;\n            continue;\n        }\n\n        if (!anchoredToHyprland && line.contains(\"Hyprland\")) {\n            Log::logger->log(Log::DEBUG, \"seekNewPageAddr: Anchored to hyprland at 0x{:x}\", start);\n            anchoredToHyprland = true;\n        } else if (start - lastEnd > PAGESIZE_VAR * 2) {\n            if (!anchoredToHyprland) {\n                Log::logger->log(Log::DEBUG, \"seekNewPageAddr: skipping gap 0x{:x}-0x{:x}, not anchored to Hyprland code pages yet.\", lastEnd, start);\n                lastStart = start;\n                lastEnd   = end;\n                continue;\n            }\n\n            Log::logger->log(Log::DEBUG, \"seekNewPageAddr: found gap: 0x{:x}-0x{:x} ({} bytes)\", lastEnd, start, start - lastEnd);\n            MAPS.close();\n            return lastEnd;\n        }\n\n        lastStart = start;\n        lastEnd   = end;\n    }\n\n    MAPS.close();\n    return 0;\n}\n\nuint64_t CHookSystem::getAddressForTrampo() {\n    // yes, technically this creates a memory leak of 64B every hook creation. But I don't care.\n    // tracking all the users of the memory would be painful.\n    // Nobody will hook 100k times, and even if, that's only 6.4 MB. Nothing.\n\n    SAllocatedPage* page = nullptr;\n    for (auto& p : m_pages) {\n        if (p.used + HOOK_TRAMPOLINE_MAX_SIZE > p.len)\n            continue;\n\n        page = &p;\n        break;\n    }\n\n    if (!page)\n        page = &m_pages.emplace_back();\n\n    if (!page->addr) {\n        // allocate it\n        Log::logger->log(Log::DEBUG, \"getAddressForTrampo: Allocating new page for hooks\");\n        const uint64_t PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE);\n        const auto     BASEPAGEADDR = seekNewPageAddr();\n        for (int attempt = 0; attempt < 2; ++attempt) {\n            for (int i = 0; i <= 2; ++i) {\n                const auto PAGEADDR = BASEPAGEADDR + i * PAGESIZE_VAR;\n\n                page->addr = rc<uint64_t>(mmap(rc<void*>(PAGEADDR), PAGESIZE_VAR, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));\n                page->len  = PAGESIZE_VAR;\n                page->used = 0;\n\n                Log::logger->log(Log::DEBUG, \"Attempted to allocate 0x{:x}, got 0x{:x}\", PAGEADDR, page->addr);\n\n                if (page->addr == rc<uint64_t>(MAP_FAILED))\n                    continue;\n                if (page->addr != PAGEADDR && attempt == 0) {\n                    munmap(rc<void*>(page->addr), PAGESIZE_VAR);\n                    page->addr = 0;\n                    page->len  = 0;\n                    continue;\n                }\n\n                break;\n            }\n            if (page->addr)\n                break;\n        }\n    }\n\n    const auto ADDRFORCONSUMER = page->addr + page->used;\n\n    page->used += HOOK_TRAMPOLINE_MAX_SIZE;\n\n    Log::logger->log(Log::DEBUG, \"getAddressForTrampo: Returning addr 0x{:x} for page at 0x{:x}\", ADDRFORCONSUMER, page->addr);\n\n    return ADDRFORCONSUMER;\n}\n"
  },
  {
    "path": "src/plugins/HookSystem.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n#include <cstddef>\n#include <unordered_set>\n#include \"../helpers/memory/Memory.hpp\"\n\n#define HANDLE                   void*\n#define HOOK_TRAMPOLINE_MAX_SIZE 32\n\nclass CFunctionHook {\n  public:\n    CFunctionHook(HANDLE owner, void* source, void* destination);\n    ~CFunctionHook();\n\n    bool hook();\n    bool unhook();\n\n    CFunctionHook(const CFunctionHook&)            = delete;\n    CFunctionHook(CFunctionHook&&)                 = delete;\n    CFunctionHook& operator=(const CFunctionHook&) = delete;\n    CFunctionHook& operator=(CFunctionHook&&)      = delete;\n\n    void*          m_original = nullptr;\n\n  private:\n    void*                      m_source               = nullptr;\n    void*                      m_launchTrampolineAddr = nullptr;\n    void*                      m_landTrampolineAddr   = nullptr;\n    void*                      m_destination          = nullptr;\n    size_t                     m_hookLen              = 0;\n    HANDLE                     m_owner                = nullptr;\n    bool                       m_active               = false;\n\n    std::vector<unsigned char> m_originalBytes;\n\n    struct SInstructionProbe {\n        size_t              len      = 0;\n        std::string         assembly = \"\";\n        std::vector<size_t> insSizes;\n    };\n\n    struct SAssembly {\n        std::vector<char> bytes;\n    };\n\n    SInstructionProbe probeMinimumJumpSize(void* start, size_t min);\n    SInstructionProbe getInstructionLenAt(void* start);\n\n    SAssembly         fixInstructionProbeRIPCalls(const SInstructionProbe& probe);\n\n    friend class CHookSystem;\n};\n\nclass CHookSystem {\n  public:\n    CFunctionHook* initHook(HANDLE handle, void* source, void* destination);\n    bool           removeHook(CFunctionHook* hook);\n\n    void           removeAllHooksFrom(HANDLE handle);\n\n  private:\n    std::vector<UP<CFunctionHook>> m_hooks;\n\n    uint64_t                       getAddressForTrampo();\n\n    struct SAllocatedPage {\n        uint64_t addr = 0;\n        uint64_t len  = 0;\n        uint64_t used = 0;\n    };\n\n    std::vector<SAllocatedPage>  m_pages;\n    std::unordered_set<uint64_t> m_activeHooks;\n\n    friend class CFunctionHook;\n};\n\ninline UP<CHookSystem> g_pFunctionHookSystem;"
  },
  {
    "path": "src/plugins/PluginAPI.cpp",
    "content": "#include \"PluginAPI.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../debug/HyprCtl.hpp\"\n#include \"../plugins/PluginSystem.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../debug/HyprNotificationOverlay.hpp\"\n#include \"../layout/target/Target.hpp\"\n#include \"../layout/supplementary/WorkspaceAlgoMatcher.hpp\"\n#include <dlfcn.h>\n#include <filesystem>\n\n#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)\n#include <sys/sysctl.h>\n#endif\n\n#include <sstream>\n\nAPICALL const char* __hyprland_api_get_hash() {\n    static auto stripPatch = [](const char* ver) -> std::string {\n        std::string_view v = ver;\n        if (!v.contains('.'))\n            return std::string{v};\n\n        return std::string{v.substr(0, v.find_last_of('.'))};\n    };\n\n    static const std::string ver = (std::string{GIT_COMMIT_HASH} + \"_aq_\" + stripPatch(AQUAMARINE_VERSION) + \"_hu_\" + stripPatch(HYPRUTILS_VERSION) + \"_hg_\" +\n                                    stripPatch(HYPRGRAPHICS_VERSION) + \"_hc_\" + stripPatch(HYPRCURSOR_VERSION) + \"_hlg_\" + stripPatch(HYPRLANG_VERSION));\n\n    return ver.c_str();\n}\n\nAPICALL SP<HOOK_CALLBACK_FN> HyprlandAPI::registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return nullptr;\n\n    //auto PFN = g_pHookSystem->hookDynamic(event, fn, handle);\n    //PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP<HOOK_CALLBACK_FN>(PFN)));\n    return nullptr;\n}\n\nAPICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP<HOOK_CALLBACK_FN> fn) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    //g_pHookSystem->unhook(fn);\n    // std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; });\n\n    return true;\n}\n\nAPICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, const std::string& args, const std::string& format) {\n    if (args.empty())\n        return g_pHyprCtl->makeDynamicCall(format + \"/\" + call);\n    else\n        return g_pHyprCtl->makeDynamicCall(format + \"/\" + call + \" \" + args);\n}\n\nAPICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) {\n    return false;\n}\n\nAPICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) {\n    return false;\n}\n\nAPICALL bool HyprlandAPI::addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::ITiledAlgorithm>()>&& factory) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    PLUGIN->m_registeredAlgos.emplace_back(name);\n\n    return Layout::Supplementary::algoMatcher()->registerTiledAlgo(name, typeInfo, std::move(factory));\n}\n\nAPICALL bool HyprlandAPI::addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::IFloatingAlgorithm>()>&& factory) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    PLUGIN->m_registeredAlgos.emplace_back(name);\n\n    return Layout::Supplementary::algoMatcher()->registerFloatingAlgo(name, typeInfo, std::move(factory));\n}\n\nAPICALL bool HyprlandAPI::removeAlgo(HANDLE handle, const std::string& name) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    std::erase(PLUGIN->m_registeredAlgos, name);\n\n    return Layout::Supplementary::algoMatcher()->unregisterAlgo(name);\n}\n\nAPICALL bool HyprlandAPI::reloadConfig() {\n    g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); });\n    return true;\n}\n\nAPICALL bool HyprlandAPI::addNotification(HANDLE handle, const std::string& text, const CHyprColor& color, const float timeMs) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    g_pHyprNotificationOverlay->addNotification(text, color, timeMs);\n\n    return true;\n}\n\nAPICALL CFunctionHook* HyprlandAPI::createFunctionHook(HANDLE handle, const void* source, const void* destination) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return nullptr;\n\n    return g_pFunctionHookSystem->initHook(handle, const_cast<void*>(source), const_cast<void*>(destination));\n}\n\nAPICALL bool HyprlandAPI::removeFunctionHook(HANDLE handle, CFunctionHook* hook) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    return g_pFunctionHookSystem->removeHook(hook);\n}\n\nAPICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, PHLWINDOW pWindow, UP<IHyprWindowDecoration> pDecoration) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    if (!validMapped(pWindow))\n        return false;\n\n    PLUGIN->m_registeredDecorations.push_back(pDecoration.get());\n\n    pWindow->addWindowDeco(std::move(pDecoration));\n\n    pWindow->layoutTarget()->recalc();\n\n    return true;\n}\n\nAPICALL bool HyprlandAPI::removeWindowDecoration(HANDLE handle, IHyprWindowDecoration* pDecoration) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        for (auto const& d : w->m_windowDecorations) {\n            if (d.get() == pDecoration) {\n                w->removeWindowDeco(pDecoration);\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nAPICALL bool HyprlandAPI::addConfigValue(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!g_pPluginSystem->m_allowConfigVars)\n        return false;\n\n    if (!PLUGIN)\n        return false;\n\n    if (!name.starts_with(\"plugin:\"))\n        return false;\n\n    g_pConfigManager->addPluginConfigVar(handle, name, value);\n    return true;\n}\n\nAPICALL bool HyprlandAPI::addConfigKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fn, Hyprlang::SHandlerOptions opts) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!g_pPluginSystem->m_allowConfigVars)\n        return false;\n\n    if (!PLUGIN)\n        return false;\n\n    g_pConfigManager->addPluginKeyword(handle, name, fn, opts);\n    return true;\n}\n\nAPICALL Hyprlang::CConfigValue* HyprlandAPI::getConfigValue(HANDLE handle, const std::string& name) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return nullptr;\n\n    if (name.starts_with(\"plugin:\"))\n        return g_pConfigManager->getHyprlangConfigValuePtr(name.substr(7), \"plugin\");\n\n    return g_pConfigManager->getHyprlangConfigValuePtr(name);\n}\n\nAPICALL void* HyprlandAPI::getFunctionAddressFromSignature(HANDLE handle, const std::string& sig) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return nullptr;\n\n    return dlsym(nullptr, sig.c_str());\n}\n\nAPICALL bool HyprlandAPI::addDispatcher(HANDLE handle, const std::string& name, std::function<void(std::string)> handler) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    PLUGIN->m_registeredDispatchers.push_back(name);\n\n    g_pKeybindManager->m_dispatchers[name] = [handler](std::string arg1) -> SDispatchResult {\n        handler(arg1);\n        return {};\n    };\n\n    return true;\n}\n\nAPICALL bool HyprlandAPI::addDispatcherV2(HANDLE handle, const std::string& name, std::function<SDispatchResult(std::string)> handler) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    PLUGIN->m_registeredDispatchers.push_back(name);\n\n    g_pKeybindManager->m_dispatchers[name] = handler;\n\n    return true;\n}\n\nAPICALL bool HyprlandAPI::removeDispatcher(HANDLE handle, const std::string& name) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    std::erase_if(g_pKeybindManager->m_dispatchers, [&](const auto& other) { return other.first == name; });\n    std::erase_if(PLUGIN->m_registeredDispatchers, [&](const auto& other) { return other == name; });\n\n    return true;\n}\n\nAPICALL bool addNotificationV2(HANDLE handle, const std::unordered_map<std::string, std::any>& data) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    try {\n        auto iterator = data.find(\"text\");\n        if (iterator == data.end())\n            return false;\n\n        // mandatory\n        std::string text;\n        try {\n            text = std::any_cast<std::string>(iterator->second);\n        } catch (std::exception& e) {\n            // attempt const char*\n            text = std::any_cast<const char*>(iterator->second);\n        }\n\n        iterator = data.find(\"time\");\n        if (iterator == data.end())\n            return false;\n\n        const auto TIME = std::any_cast<uint64_t>(iterator->second);\n\n        iterator = data.find(\"color\");\n        if (iterator == data.end())\n            return false;\n\n        const auto COLOR = std::any_cast<CHyprColor>(iterator->second);\n\n        // optional\n        eIcons icon = ICON_NONE;\n        iterator    = data.find(\"icon\");\n        if (iterator != data.end())\n            icon = std::any_cast<eIcons>(iterator->second);\n\n        g_pHyprNotificationOverlay->addNotification(text, COLOR, TIME, icon);\n\n    } catch (std::exception& e) {\n        // bad any_cast most likely, plugin error\n        return false;\n    }\n\n    return true;\n}\n\nAPICALL std::vector<SFunctionMatch> HyprlandAPI::findFunctionsByName(HANDLE handle, const std::string& name) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return std::vector<SFunctionMatch>{};\n\n#if defined(KERN_PROC_PATHNAME)\n    int mib[] = {\n        CTL_KERN,\n#if defined(__NetBSD__)\n        KERN_PROC_ARGS,\n        -1,\n        KERN_PROC_PATHNAME,\n#else\n        KERN_PROC,\n        KERN_PROC_PATHNAME,\n        -1,\n#endif\n    };\n    u_int  miblen        = sizeof(mib) / sizeof(mib[0]);\n    char   exe[PATH_MAX] = \"/nonexistent\";\n    size_t sz            = sizeof(exe);\n    sysctl(mib, miblen, &exe, &sz, NULL, 0);\n    const auto FPATH = std::filesystem::canonical(exe);\n#elif defined(__OpenBSD__)\n    // Neither KERN_PROC_PATHNAME nor /proc are supported\n    const auto FPATH = std::filesystem::canonical(\"/usr/local/bin/Hyprland\");\n#else\n    const auto FPATH = std::filesystem::canonical(\"/proc/self/exe\");\n#endif\n\n#ifdef __clang__\n    static const auto SYMBOLS          = execAndGet((\"llvm-nm -D -j \\\"\" + FPATH.string() + \"\\\"\").c_str());\n    static const auto SYMBOLSDEMANGLED = execAndGet((\"llvm-nm -D -j --demangle \\\"\" + FPATH.string() + \"\\\"\").c_str());\n#else\n    static const auto SYMBOLS          = execAndGet((\"nm -D -j \\\"\" + FPATH.string() + \"\\\"\").c_str());\n    static const auto SYMBOLSDEMANGLED = execAndGet((\"nm -D -j --demangle=auto \\\"\" + FPATH.string() + \"\\\"\").c_str());\n#endif\n\n    auto demangledFromID = [&](size_t id) -> std::string {\n        size_t pos   = 0;\n        size_t count = 0;\n        while (count < id) {\n            pos++;\n            pos = SYMBOLSDEMANGLED.find('\\n', pos);\n            if (pos == std::string::npos)\n                return \"\";\n            count++;\n        }\n\n        // Skip the newline char itself\n        if (pos != 0)\n            pos++;\n\n        return SYMBOLSDEMANGLED.substr(pos, SYMBOLSDEMANGLED.find('\\n', pos) - pos);\n    };\n\n    if (SYMBOLS.empty()) {\n        Log::logger->log(Log::ERR, R\"(Unable to search for function \"{}\": no symbols found in binary (is \"{}\" in path?))\", name,\n#ifdef __clang__\n                         \"llvm-nm\"\n#else\n                         \"nm\"\n#endif\n        );\n        return {};\n    }\n\n    std::vector<SFunctionMatch> matches;\n\n    std::istringstream          inStream(SYMBOLS);\n    std::string                 line;\n    int                         lineNo = 0;\n    while (std::getline(inStream, line)) {\n        if (line.contains(name)) {\n            void* address = dlsym(nullptr, line.c_str());\n\n            if (!address)\n                continue;\n\n            matches.push_back({address, line, demangledFromID(lineNo)});\n        }\n        lineNo++;\n    }\n\n    return matches;\n}\n\nAPICALL SVersionInfo HyprlandAPI::getHyprlandVersion(HANDLE handle) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return {};\n\n    return {GIT_COMMIT_HASH, GIT_TAG, GIT_DIRTY != std::string(\"\"), GIT_BRANCH, GIT_COMMIT_MESSAGE, GIT_COMMITS};\n}\n\nAPICALL SP<SHyprCtlCommand> HyprlandAPI::registerHyprCtlCommand(HANDLE handle, SHyprCtlCommand cmd) {\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return nullptr;\n\n    auto PTR = g_pHyprCtl->registerCommand(cmd);\n    PLUGIN->m_registeredHyprctlCommands.push_back(PTR);\n    return PTR;\n}\n\nAPICALL bool HyprlandAPI::unregisterHyprCtlCommand(HANDLE handle, SP<SHyprCtlCommand> cmd) {\n\n    auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);\n\n    if (!PLUGIN)\n        return false;\n\n    std::erase_if(PLUGIN->m_registeredHyprctlCommands, [&](const auto& other) { return !other || other == cmd; });\n    g_pHyprCtl->unregisterCommand(cmd);\n\n    return true;\n}\n"
  },
  {
    "path": "src/plugins/PluginAPI.hpp",
    "content": "#pragma once\n\n/*\n\nHyprland Plugin API.\n\nMost documentation will be made with comments in this code, but more info can be also found on the wiki.\n\n!WARNING!\nThe Hyprland API passes C++ objects over, so no ABI compatibility is guaranteed.\nMake sure to compile your plugins with the same compiler as Hyprland, and ideally,\non the same machine.\n\nSee examples/examplePlugin for an example plugin\n\n * NOTE:\nFeel like the API is missing something you'd like to use in your plugin? Open an issue on github!\n\n*/\n\n#define HYPRLAND_API_VERSION \"0.1\"\n\n#include \"../helpers/Color.hpp\"\n#include \"HookSystem.hpp\"\n#include \"../SharedDefs.hpp\"\n#include \"../defines.hpp\"\n#include \"../version.h\"\n\n#include <any>\n#include <functional>\n#include <string>\n#include <string_view>\n#include <hyprlang.hpp>\n\nusing PLUGIN_DESCRIPTION_INFO = struct {\n    std::string name;\n    std::string description;\n    std::string author;\n    std::string version;\n};\n\nstruct SFunctionMatch {\n    void*       address = nullptr;\n    std::string signature;\n    std::string demangled;\n};\n\nstruct SVersionInfo {\n    std::string hash;\n    std::string tag;\n    bool        dirty = false;\n    std::string branch;\n    std::string message;\n    std::string commits;\n};\n\n#define APICALL extern \"C\"\n#define EXPORT  __attribute__((visibility(\"default\")))\n#define REQUIRED\n#define OPTIONAL\n#define HANDLE void*\n\n// C ABI is needed to prevent symbol mangling, but we don't actually need C compatibility,\n// so we ignore this warning about return types that are potentially incompatible with C.\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wreturn-type-c-linkage\"\n#endif\n\nclass IHyprLayout;\nclass IHyprWindowDecoration;\nstruct SConfigValue;\nclass Hypr_dummyClass {};\n\nnamespace Layout {\n    class ITiledAlgorithm;\n    class IFloatingAlgorithm;\n};\n\nusing HOOK_CALLBACK_FN = Hypr_dummyClass;\n\n/*\n    These methods are for the plugin to implement\n    Methods marked with REQUIRED are required.\n*/\n\n/*\n    called pre-plugin init.\n    In case of a version mismatch, will eject the .so.\n\n    This function should not be modified, see the example plugin.\n*/\nusing PPLUGIN_API_VERSION_FUNC = REQUIRED std::string (*)();\n#define PLUGIN_API_VERSION          pluginAPIVersion\n#define PLUGIN_API_VERSION_FUNC_STR \"pluginAPIVersion\"\n\n/*\n    called on plugin init. Passes a handle as the parameter, which the plugin should keep for identification later.\n    The plugin should return a PLUGIN_DESCRIPTION_INFO struct with information about itself.\n\n    Keep in mind this is executed synchronously, and as such any blocking calls to hyprland might hang. (e.g. system(\"hyprctl ...\"))\n*/\nusing PPLUGIN_INIT_FUNC = REQUIRED PLUGIN_DESCRIPTION_INFO (*)(HANDLE);\n#define PLUGIN_INIT          pluginInit\n#define PLUGIN_INIT_FUNC_STR \"pluginInit\"\n\n/*\n    called on plugin unload, if that was a user action. If the plugin is being unloaded by an error,\n    this will not be called.\n\n    Hooks are unloaded after exit.\n*/\nusing PPLUGIN_EXIT_FUNC = OPTIONAL void (*)();\n#define PLUGIN_EXIT          pluginExit\n#define PLUGIN_EXIT_FUNC_STR \"pluginExit\"\n\n/*\n    End plugin methods\n*/\n\n// NOLINTNEXTLINE(readability-identifier-naming)\nnamespace HyprlandAPI {\n\n    /*\n        Add a config value.\n        All config values MUST be in the plugin: namespace\n        This method may only be called in \"pluginInit\"\n\n        After you have registered ALL of your config values, you may call `getConfigValue`\n\n        returns: true on success, false on fail\n    */\n    APICALL bool addConfigValue(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value);\n\n    /*\n        Add a config keyword.\n        This method may only be called in \"pluginInit\"\n\n        returns: true on success, false on fail\n    */\n    APICALL bool addConfigKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fn, Hyprlang::SHandlerOptions opts);\n\n    /*\n        Get a config value.\n\n        Please see the <hyprlang.hpp> header or https://hypr.land/hyprlang/ for docs regarding Hyprlang types.\n\n        returns: a pointer to the config value struct, which is guaranteed to be valid for the life of this plugin, unless another `addConfigValue` is called afterwards.\n                nullptr on error.\n    */\n    APICALL Hyprlang::CConfigValue* getConfigValue(HANDLE handle, const std::string& name);\n\n    /*\n        Deprecated: doesn't do anything anymore, use Event::bus()\n\n        Register a dynamic (function) callback to a selected event.\n        Pointer will be free'd by Hyprland on unregisterCallback().\n\n        returns: a pointer to the newly allocated function. nullptr on fail.\n\n        WARNING: Losing this pointer will unregister the callback!\n    */\n    APICALL [[deprecated]] [[nodiscard]] SP<HOOK_CALLBACK_FN> registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn);\n\n    /*\n        Unregisters a callback. If the callback was dynamic, frees the memory.\n\n        returns: true on success, false on fail\n\n        Deprecated: just reset the pointer you received with registerCallbackDynamic\n    */\n    APICALL [[deprecated]] bool unregisterCallback(HANDLE handle, SP<HOOK_CALLBACK_FN> fn);\n\n    /*\n        Calls a hyprctl command.\n\n        returns: the output (as in hyprctl)\n    */\n    APICALL std::string invokeHyprctlCommand(const std::string& call, const std::string& args, const std::string& format = \"\");\n\n    /*\n        Adds a layout to Hyprland.\n\n        returns: true on success. False otherwise.\n\n        deprecated: addTiledAlgo, addFloatingAlgo\n    */\n    APICALL [[deprecated]] bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout);\n\n    /*\n        Removes an added layout from Hyprland.\n\n        returns: true on success. False otherwise.\n\n        deprecated: V2 removeAlgo\n    */\n    APICALL [[deprecated]] bool removeLayout(HANDLE handle, IHyprLayout* layout);\n\n    /*\n        Algorithm fns. Used for registering and removing. Return success.\n    */\n    APICALL bool addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::ITiledAlgorithm>()>&& factory);\n    APICALL bool addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function<UP<Layout::IFloatingAlgorithm>()>&& factory);\n    APICALL bool removeAlgo(HANDLE handle, const std::string& name);\n\n    /*\n        Queues a config reload. Does not take effect immediately.\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool reloadConfig();\n\n    /*\n        Adds a notification.\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool addNotification(HANDLE handle, const std::string& text, const CHyprColor& color, const float timeMs);\n\n    /*\n        Creates a trampoline function hook to an internal hl func.\n\n        returns: CFunctionHook*\n\n        !WARNING! Hooks are *not* guaranteed any API stability. Internal methods may be removed, added, or renamed. Consider preferring the API whenever possible.\n    */\n    APICALL CFunctionHook* createFunctionHook(HANDLE handle, const void* source, const void* destination);\n\n    /*\n        Removes a trampoline function hook. Will unhook if still hooked.\n\n        returns: true on success. False otherwise.\n\n        !WARNING! Hooks are *not* guaranteed any API stability. Internal methods may be removed, added, or renamed. Consider preferring the API whenever possible.\n    */\n    APICALL bool removeFunctionHook(HANDLE handle, CFunctionHook* hook);\n\n    /*\n        Gets a function address from a signature.\n        This is useful for hooking private functions.\n\n        returns: function address, or nullptr on fail.\n\n        Deprecated because of findFunctionsByName.\n    */\n    APICALL [[deprecated]] void* getFunctionAddressFromSignature(HANDLE handle, const std::string& sig);\n\n    /*\n        Adds a window decoration to a window\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool addWindowDecoration(HANDLE handle, PHLWINDOW pWindow, UP<IHyprWindowDecoration> pDecoration);\n\n    /*\n        Removes a window decoration\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool removeWindowDecoration(HANDLE handle, IHyprWindowDecoration* pDecoration);\n\n    /*\n        Adds a keybind dispatcher.\n\n        returns: true on success. False otherwise.\n\n        DEPRECATED: use addDispatcherV2\n    */\n    APICALL [[deprecated]] bool addDispatcher(HANDLE handle, const std::string& name, std::function<void(std::string)> handler);\n\n    /*\n        Adds a keybind dispatcher.\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool addDispatcherV2(HANDLE handle, const std::string& name, std::function<SDispatchResult(std::string)> handler);\n\n    /*\n        Removes a keybind dispatcher.\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool removeDispatcher(HANDLE handle, const std::string& name);\n\n    /*\n        Adds a notification.\n\n        data has to contain:\n         - text: std::string or const char*\n         - time: uint64_t\n         - color: CHyprColor -> CHyprColor(0) will apply the default color for the notification icon\n\n        data may contain:\n         - icon: eIcons\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool addNotificationV2(HANDLE handle, const std::unordered_map<std::string, std::any>& data);\n\n    /*\n        Returns a vector of found functions matching the provided name.\n\n        These addresses will not change, and should be made static. Lookups are slow.\n\n        Empty means either none found or handle was invalid\n    */\n    APICALL std::vector<SFunctionMatch> findFunctionsByName(HANDLE handle, const std::string& name);\n\n    /*\n        Returns the hyprland version data. It's highly advised to not run plugins compiled\n        for a different hash.\n    */\n    APICALL SVersionInfo getHyprlandVersion(HANDLE handle);\n\n    /*\n        Registers a hyprctl command\n\n        returns: Pointer. Nullptr on fail.\n    */\n    APICALL SP<SHyprCtlCommand> registerHyprCtlCommand(HANDLE handle, SHyprCtlCommand cmd);\n\n    /*\n        Unregisters a hyprctl command\n\n        returns: true on success. False otherwise.\n    */\n    APICALL bool unregisterHyprCtlCommand(HANDLE handle, SP<SHyprCtlCommand> cmd);\n};\n\n// NOLINTBEGIN\n/*\n    Get the descriptive string this plugin/server was compiled with.\n\n    This function will end up in both hyprland and any/all plugins,\n    and can be found by a simple dlsym()\n\n    _get_hash() is server,\n    _get_client_hash() is client.\n*/\nAPICALL EXPORT const char*        __hyprland_api_get_hash();\nAPICALL inline EXPORT const char* __hyprland_api_get_client_hash() {\n    static auto stripPatch = [](const char* ver) -> std::string {\n        std::string_view v = ver;\n        if (!v.contains('.'))\n            return std::string{v};\n\n        return std::string{v.substr(0, v.find_last_of('.'))};\n    };\n\n    static const std::string ver = (std::string{GIT_COMMIT_HASH} + \"_aq_\" + stripPatch(AQUAMARINE_VERSION) + \"_hu_\" + stripPatch(HYPRUTILS_VERSION) + \"_hg_\" +\n                                    stripPatch(HYPRGRAPHICS_VERSION) + \"_hc_\" + stripPatch(HYPRCURSOR_VERSION) + \"_hlg_\" + stripPatch(HYPRLANG_VERSION));\n\n    return ver.c_str();\n}\n// NOLINTEND\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n"
  },
  {
    "path": "src/plugins/PluginSystem.cpp",
    "content": "#include \"PluginSystem.hpp\"\n\n#include <dlfcn.h>\n#include <ranges>\n#include \"../config/ConfigManager.hpp\"\n#include \"../debug/HyprCtl.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../managers/permissions/DynamicPermissionManager.hpp\"\n#include \"../debug/HyprNotificationOverlay.hpp\"\n#include \"../layout/supplementary/WorkspaceAlgoMatcher.hpp\"\n#include \"../i18n/Engine.hpp\"\n\nCPluginSystem::CPluginSystem() {\n    g_pFunctionHookSystem = makeUnique<CHookSystem>();\n}\n\nSP<CPromise<CPlugin*>> CPluginSystem::loadPlugin(const std::string& path, eSpecialPidTypes pidType) {\n\n    pid_t pid = 0;\n\n    if (g_pHyprCtl->m_currentRequestParams.pid > 0)\n        pid = g_pHyprCtl->m_currentRequestParams.pid;\n\n    return CPromise<CPlugin*>::make([path, pid, pidType, this](SP<CPromiseResolver<CPlugin*>> resolver) {\n        const auto PERM = g_pDynamicPermissionManager->clientPermissionModeWithString(pidType != SPECIAL_PID_TYPE_NONE ? pidType : pid, path, PERMISSION_TYPE_PLUGIN);\n        if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {\n            Log::logger->log(Log::DEBUG, \"CPluginSystem: Waiting for user confirmation to load {}\", path);\n\n            auto promise = g_pDynamicPermissionManager->promiseFor(pid, path, PERMISSION_TYPE_PLUGIN);\n            if (!promise) { // already awaiting or something?\n                resolver->reject(\"Failed to get a promise for permission\");\n                return;\n            }\n\n            promise->then([this, path, resolver](SP<CPromiseResult<eDynamicPermissionAllowMode>> result) {\n                if (result->hasError()) {\n                    Log::logger->log(Log::ERR, \"CPluginSystem: Error spawning permission prompt\");\n                    resolver->reject(\"Error spawning permission prompt\");\n                    return;\n                }\n\n                if (result->result() != PERMISSION_RULE_ALLOW_MODE_ALLOW) {\n                    Log::logger->log(Log::ERR, \"CPluginSystem: Rejecting plugin load of {}, user denied\", path);\n                    resolver->reject(\"user denied\");\n                    return;\n                }\n\n                Log::logger->log(Log::DEBUG, \"CPluginSystem: Loading {}, user allowed\", path);\n\n                const auto RESULT = loadPluginInternal(path);\n                if (RESULT.has_value())\n                    resolver->resolve(RESULT.value());\n                else\n                    resolver->reject(RESULT.error());\n            });\n            return;\n        } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) {\n            Log::logger->log(Log::DEBUG, \"CPluginSystem: Rejecting plugin load, permission is disabled\");\n            resolver->reject(\"permission is disabled\");\n            return;\n        }\n\n        const auto RESULT = loadPluginInternal(path);\n        if (RESULT.has_value())\n            resolver->resolve(RESULT.value());\n        else\n            resolver->reject(RESULT.error());\n    });\n}\n\nstd::expected<CPlugin*, std::string> CPluginSystem::loadPluginInternal(const std::string& path) {\n    if (getPluginByPath(path)) {\n        Log::logger->log(Log::ERR, \" [PluginSystem] Cannot load a plugin twice!\");\n        return std::unexpected(\"Cannot load a plugin twice!\");\n    }\n\n    auto* const PLUGIN = m_loadedPlugins.emplace_back(makeUnique<CPlugin>()).get();\n\n    PLUGIN->m_path = path;\n\n    HANDLE MODULE = dlopen(path.c_str(), RTLD_LAZY);\n\n    if (!MODULE) {\n        std::string strerr = dlerror();\n        Log::logger->log(Log::ERR, \" [PluginSystem] Plugin {} could not be loaded: {}\", path, strerr);\n        m_loadedPlugins.pop_back();\n        return std::unexpected(std::format(\"Plugin {} could not be loaded: {}\", path, strerr));\n    }\n\n    PLUGIN->m_handle = MODULE;\n\n    PPLUGIN_API_VERSION_FUNC apiVerFunc = rc<PPLUGIN_API_VERSION_FUNC>(dlsym(MODULE, PLUGIN_API_VERSION_FUNC_STR));\n    PPLUGIN_INIT_FUNC        initFunc   = rc<PPLUGIN_INIT_FUNC>(dlsym(MODULE, PLUGIN_INIT_FUNC_STR));\n\n    if (!apiVerFunc || !initFunc) {\n        Log::logger->log(Log::ERR, \" [PluginSystem] Plugin {} could not be loaded. (No apiver/init func)\", path);\n        dlclose(MODULE);\n        m_loadedPlugins.pop_back();\n        return std::unexpected(std::format(\"Plugin {} could not be loaded: {}\", path, \"missing apiver/init func\"));\n    }\n\n    const std::string PLUGINAPIVER = apiVerFunc();\n\n    if (PLUGINAPIVER != HYPRLAND_API_VERSION) {\n        Log::logger->log(Log::ERR, \" [PluginSystem] Plugin {} could not be loaded. (API version mismatch)\", path);\n        dlclose(MODULE);\n        m_loadedPlugins.pop_back();\n        return std::unexpected(std::format(\"Plugin {} could not be loaded: {}\", path, \"API version mismatch\"));\n    }\n\n    PLUGIN_DESCRIPTION_INFO PLUGINDATA;\n\n    try {\n        if (!setjmp(m_pluginFaultJumpBuf)) {\n            m_allowConfigVars = true;\n            PLUGINDATA        = initFunc(MODULE);\n        } else {\n            // this module crashed.\n            throw std::runtime_error(\"received a fatal signal\");\n        }\n    } catch (std::exception& e) {\n        m_allowConfigVars = false;\n        Log::logger->log(Log::ERR, \" [PluginSystem] Plugin {} (Handle {:x}) crashed in init. Unloading.\", path, rc<uintptr_t>(MODULE));\n        unloadPlugin(PLUGIN, true); // Plugin could've already hooked/done something\n        return std::unexpected(std::format(\"Plugin {} could not be loaded: plugin crashed/threw in main: {}\", path, e.what()));\n    }\n\n    m_allowConfigVars = false;\n\n    PLUGIN->m_author      = PLUGINDATA.author;\n    PLUGIN->m_description = PLUGINDATA.description;\n    PLUGIN->m_version     = PLUGINDATA.version;\n    PLUGIN->m_name        = PLUGINDATA.name;\n\n    g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); });\n\n    Log::logger->log(Log::DEBUG, R\"( [PluginSystem] Plugin {} loaded. Handle: {:x}, path: \"{}\", author: \"{}\", description: \"{}\", version: \"{}\")\", PLUGINDATA.name,\n                     rc<uintptr_t>(MODULE), path, PLUGINDATA.author, PLUGINDATA.description, PLUGINDATA.version);\n\n    return PLUGIN;\n}\n\nvoid CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) {\n    if (!plugin)\n        return;\n\n    if (!eject) {\n        PPLUGIN_EXIT_FUNC exitFunc = rc<PPLUGIN_EXIT_FUNC>(dlsym(plugin->m_handle, PLUGIN_EXIT_FUNC_STR));\n        if (exitFunc)\n            exitFunc();\n    }\n\n    // for (auto const& [k, v] : plugin->m_registeredCallbacks) {\n    //     if (const auto SHP = v.lock())\n    //         g_pHookSystem->unhook(SHP);\n    // }\n\n    for (const auto& l : plugin->m_registeredAlgos) {\n        Layout::Supplementary::algoMatcher()->unregisterAlgo(l);\n    }\n\n    g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_handle);\n\n    const auto rd = plugin->m_registeredDecorations;\n    for (auto const& d : rd)\n        HyprlandAPI::removeWindowDecoration(plugin->m_handle, d);\n\n    const auto rdi = plugin->m_registeredDispatchers;\n    for (auto const& d : rdi)\n        HyprlandAPI::removeDispatcher(plugin->m_handle, d);\n\n    const auto rhc = plugin->m_registeredHyprctlCommands;\n    for (auto const& c : rhc) {\n        if (const auto sp = c.lock())\n            HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, sp);\n    }\n\n    g_pConfigManager->removePluginConfig(plugin->m_handle);\n\n    // save these two for dlclose and a log,\n    // as erase_if will kill the pointer\n    const auto PLNAME   = plugin->m_name;\n    const auto PLHANDLE = plugin->m_handle;\n\n    std::erase_if(m_loadedPlugins, [&](const auto& other) { return other->m_handle == PLHANDLE; });\n\n    dlclose(PLHANDLE);\n\n    Log::logger->log(Log::DEBUG, \" [PluginSystem] Plugin {} unloaded.\", PLNAME);\n\n    // reload config to fix some stuf like e.g. unloadedPluginVars\n    g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); });\n}\n\nvoid CPluginSystem::unloadAllPlugins() {\n    for (auto const& p : m_loadedPlugins | std::views::reverse)\n        unloadPlugin(p.get(), false); // Unload remaining plugins gracefully\n}\n\nvoid CPluginSystem::updateConfigPlugins(const std::vector<std::string>& plugins, bool& changed) {\n    if (m_lastConfigPlugins == plugins)\n        return;\n\n    m_lastConfigPlugins = plugins;\n\n    // unload all plugins that are no longer present\n    for (auto const& p : m_loadedPlugins | std::views::reverse) {\n        if (!p->m_loadedWithConfig || std::ranges::find(plugins, p->m_path) != plugins.end())\n            continue;\n\n        Log::logger->log(Log::DEBUG, \"Unloading plugin {} which is no longer present in config\", p->m_path);\n        unloadPlugin(p.get(), false);\n        changed = true;\n    }\n\n    // load all new plugins\n    for (auto const& path : plugins) {\n        if (std::ranges::find_if(m_loadedPlugins, [&](const auto& other) { return other->m_path == path; }) != m_loadedPlugins.end())\n            continue;\n\n        Log::logger->log(Log::DEBUG, \"Loading plugin {} which is now present in config\", path);\n\n        changed = true;\n\n        loadPlugin(path, SPECIAL_PID_TYPE_CONFIG)->then([path](SP<CPromiseResult<CPlugin*>> result) {\n            if (result->hasError()) {\n                const auto NAME = path.contains('/') ? path.substr(path.find_last_of('/') + 1) : path;\n                Log::logger->log(Log::ERR, \"CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}\", NAME, result->error());\n                g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{\"name\", NAME}, {\"error\", result->error()}}),\n                                                            CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR);\n                return;\n            }\n\n            result->result()->m_loadedWithConfig = true;\n\n            Log::logger->log(Log::DEBUG, \"CPluginSystem::updateConfigPlugins: loaded {}\", path);\n        });\n    }\n}\n\nCPlugin* CPluginSystem::getPluginByPath(const std::string& path) {\n    for (auto const& p : m_loadedPlugins) {\n        if (p->m_path == path)\n            return p.get();\n    }\n\n    return nullptr;\n}\n\nCPlugin* CPluginSystem::getPluginByHandle(HANDLE handle) {\n    for (auto const& p : m_loadedPlugins) {\n        if (p->m_handle == handle)\n            return p.get();\n    }\n\n    return nullptr;\n}\n\nstd::vector<CPlugin*> CPluginSystem::getAllPlugins() {\n    std::vector<CPlugin*> results(m_loadedPlugins.size());\n    for (size_t i = 0; i < m_loadedPlugins.size(); ++i)\n        results[i] = m_loadedPlugins[i].get();\n    return results;\n}\n\nsize_t CPluginSystem::pluginCount() {\n    return m_loadedPlugins.size();\n}\n\nvoid CPluginSystem::sigGetPlugins(CPlugin** data, size_t len) {\n    for (size_t i = 0; i < std::min(m_loadedPlugins.size(), len); i++) {\n        data[i] = m_loadedPlugins[i].get();\n    }\n}\n"
  },
  {
    "path": "src/plugins/PluginSystem.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/defer/Promise.hpp\"\n#include \"../helpers/time/Timer.hpp\"\n#include \"PluginAPI.hpp\"\n#include \"../managers/permissions/DynamicPermissionManager.hpp\"\n#include <csetjmp>\n#include <expected>\n\nclass IHyprWindowDecoration;\n\nclass CPlugin {\n  public:\n    std::string                         m_name        = \"\";\n    std::string                         m_description = \"\";\n    std::string                         m_author      = \"\";\n    std::string                         m_version     = \"\";\n\n    std::string                         m_path = \"\";\n\n    bool                                m_loadedWithConfig = false;\n\n    HANDLE                              m_handle = nullptr;\n\n    std::vector<IHyprWindowDecoration*> m_registeredDecorations;\n    //std::vector<std::pair<std::string, WP<HOOK_CALLBACK_FN>>> m_registeredCallbacks;\n    std::vector<std::string>         m_registeredDispatchers;\n    std::vector<WP<SHyprCtlCommand>> m_registeredHyprctlCommands;\n    std::vector<std::string>         m_registeredAlgos;\n};\n\nclass CPluginSystem {\n  public:\n    CPluginSystem();\n\n    SP<CPromise<CPlugin*>> loadPlugin(const std::string& path, eSpecialPidTypes pidType = SPECIAL_PID_TYPE_NONE);\n    void                   unloadPlugin(const CPlugin* plugin, bool eject = false);\n    void                   unloadAllPlugins();\n    void                   updateConfigPlugins(const std::vector<std::string>& plugins, bool& changed);\n    CPlugin*               getPluginByPath(const std::string& path);\n    CPlugin*               getPluginByHandle(HANDLE handle);\n    std::vector<CPlugin*>  getAllPlugins();\n    size_t                 pluginCount();\n    void                   sigGetPlugins(CPlugin** data, size_t len);\n\n    bool                   m_allowConfigVars = false;\n\n  private:\n    std::vector<UP<CPlugin>>             m_loadedPlugins;\n    std::vector<std::string>             m_lastConfigPlugins;\n    jmp_buf                              m_pluginFaultJumpBuf;\n\n    std::expected<CPlugin*, std::string> loadPluginInternal(const std::string& path);\n};\n\ninline UP<CPluginSystem> g_pPluginSystem;\n"
  },
  {
    "path": "src/protocols/AlphaModifier.cpp",
    "content": "#include \"AlphaModifier.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"alpha-modifier-v1.hpp\"\n#include \"core/Compositor.hpp\"\n\nCAlphaModifier::CAlphaModifier(UP<CWpAlphaModifierSurfaceV1>&& resource, SP<CWLSurfaceResource> surface) : m_surface(surface) {\n    setResource(std::move(resource));\n}\n\nbool CAlphaModifier::good() {\n    return m_resource->resource();\n}\n\nvoid CAlphaModifier::setResource(UP<CWpAlphaModifierSurfaceV1>&& resource) {\n    m_resource = std::move(resource);\n\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CWpAlphaModifierSurfaceV1* resource) { destroy(); });\n    m_resource->setOnDestroy([this](CWpAlphaModifierSurfaceV1* resource) { destroy(); });\n\n    m_resource->setSetMultiplier([this](CWpAlphaModifierSurfaceV1* resource, uint32_t alpha) {\n        if (!m_surface) {\n            m_resource->error(WP_ALPHA_MODIFIER_SURFACE_V1_ERROR_NO_SURFACE, \"set_multiplier called for destroyed wl_surface\");\n            return;\n        }\n\n        m_alpha = alpha / sc<float>(UINT32_MAX);\n    });\n\n    m_listeners.surfaceCommitted = m_surface->m_events.commit.listen([this] {\n        auto surface = Desktop::View::CWLSurface::fromResource(m_surface.lock());\n\n        if (surface && surface->m_alphaModifier != m_alpha) {\n            surface->m_alphaModifier = m_alpha;\n            auto box                 = surface->getSurfaceBoxGlobal();\n\n            if (box.has_value())\n                g_pHyprRenderer->damageBox(*box);\n\n            if (!m_resource)\n                PROTO::alphaModifier->destroyAlphaModifier(this);\n        }\n    });\n\n    m_listeners.surfaceDestroyed = m_surface->m_events.destroy.listen([this] {\n        if (!m_resource)\n            PROTO::alphaModifier->destroyAlphaModifier(this);\n    });\n}\n\nvoid CAlphaModifier::destroy() {\n    m_resource.reset();\n    m_alpha = 1.F;\n\n    if (!m_surface)\n        PROTO::alphaModifier->destroyAlphaModifier(this);\n}\n\nCAlphaModifierProtocol::CAlphaModifierProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CAlphaModifierProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CWpAlphaModifierV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CWpAlphaModifierV1* manager) { destroyManager(manager); });\n\n    RESOURCE->setDestroy([this](CWpAlphaModifierV1* manager) { destroyManager(manager); });\n    RESOURCE->setGetSurface([this](CWpAlphaModifierV1* manager, uint32_t id, wl_resource* surface) { getSurface(manager, id, CWLSurfaceResource::fromResource(surface)); });\n}\n\nvoid CAlphaModifierProtocol::destroyManager(CWpAlphaModifierV1* manager) {\n    std::erase_if(m_managers, [&](const auto& p) { return p.get() == manager; });\n}\n\nvoid CAlphaModifierProtocol::destroyAlphaModifier(CAlphaModifier* modifier) {\n    std::erase_if(m_alphaModifiers, [&](const auto& entry) { return entry.second.get() == modifier; });\n}\n\nvoid CAlphaModifierProtocol::getSurface(CWpAlphaModifierV1* manager, uint32_t id, SP<CWLSurfaceResource> surface) {\n    CAlphaModifier* alphaModifier = nullptr;\n    auto            iter          = std::ranges::find_if(m_alphaModifiers, [&](const auto& entry) { return entry.second->m_surface == surface; });\n\n    if (iter != m_alphaModifiers.end()) {\n        if (iter->second->m_resource) {\n            LOGM(Log::ERR, \"AlphaModifier already present for surface {:x}\", (uintptr_t)surface.get());\n            manager->error(WP_ALPHA_MODIFIER_V1_ERROR_ALREADY_CONSTRUCTED, \"AlphaModifier already present\");\n            return;\n        } else {\n            iter->second->setResource(makeUnique<CWpAlphaModifierSurfaceV1>(manager->client(), manager->version(), id));\n            alphaModifier = iter->second.get();\n        }\n    } else {\n        alphaModifier = m_alphaModifiers.emplace(surface, makeUnique<CAlphaModifier>(makeUnique<CWpAlphaModifierSurfaceV1>(manager->client(), manager->version(), id), surface))\n                            .first->second.get();\n    }\n\n    if UNLIKELY (!alphaModifier->good()) {\n        manager->noMemory();\n        m_alphaModifiers.erase(surface);\n    }\n}\n"
  },
  {
    "path": "src/protocols/AlphaModifier.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"alpha-modifier-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\nclass CAlphaModifierProtocol;\n\nclass CAlphaModifier {\n  public:\n    CAlphaModifier(UP<CWpAlphaModifierSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface);\n\n    bool good();\n    void setResource(UP<CWpAlphaModifierSurfaceV1>&& resource);\n\n  private:\n    UP<CWpAlphaModifierSurfaceV1> m_resource;\n    WP<CWLSurfaceResource>        m_surface;\n    float                         m_alpha = 1.0;\n\n    void                          destroy();\n\n    struct {\n        CHyprSignalListener surfaceCommitted;\n        CHyprSignalListener surfaceDestroyed;\n    } m_listeners;\n\n    friend class CAlphaModifierProtocol;\n};\n\nclass CAlphaModifierProtocol : public IWaylandProtocol {\n  public:\n    CAlphaModifierProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyManager(CWpAlphaModifierV1* res);\n    void destroyAlphaModifier(CAlphaModifier* surface);\n    void getSurface(CWpAlphaModifierV1* manager, uint32_t id, SP<CWLSurfaceResource> surface);\n\n    //\n    std::vector<UP<CWpAlphaModifierV1>>                            m_managers;\n    std::unordered_map<WP<CWLSurfaceResource>, UP<CAlphaModifier>> m_alphaModifiers;\n\n    friend class CAlphaModifier;\n};\n\nnamespace PROTO {\n    inline UP<CAlphaModifierProtocol> alphaModifier;\n};\n"
  },
  {
    "path": "src/protocols/CTMControl.cpp",
    "content": "#include \"CTMControl.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"core/Output.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"managers/animation/AnimationManager.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\n\nCHyprlandCTMControlResource::CHyprlandCTMControlResource(UP<CHyprlandCtmControlManagerV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CHyprlandCtmControlManagerV1* pMgr) { PROTO::ctm->destroyResource(this); });\n    m_resource->setOnDestroy([this](CHyprlandCtmControlManagerV1* pMgr) { PROTO::ctm->destroyResource(this); });\n\n    m_resource->setSetCtmForOutput([this](CHyprlandCtmControlManagerV1* r, wl_resource* output, wl_fixed_t mat0, wl_fixed_t mat1, wl_fixed_t mat2, wl_fixed_t mat3, wl_fixed_t mat4,\n                                          wl_fixed_t mat5, wl_fixed_t mat6, wl_fixed_t mat7, wl_fixed_t mat8) {\n        if (m_blocked)\n            return;\n\n        const auto OUTPUTRESOURCE = CWLOutputResource::fromResource(output);\n\n        if UNLIKELY (!OUTPUTRESOURCE)\n            return; // ?!\n\n        const auto PMONITOR = OUTPUTRESOURCE->m_monitor.lock();\n\n        if UNLIKELY (!PMONITOR)\n            return; // ?!?!\n\n        const std::array<float, 9> MAT = {wl_fixed_to_double(mat0), wl_fixed_to_double(mat1), wl_fixed_to_double(mat2), wl_fixed_to_double(mat3), wl_fixed_to_double(mat4),\n                                          wl_fixed_to_double(mat5), wl_fixed_to_double(mat6), wl_fixed_to_double(mat7), wl_fixed_to_double(mat8)};\n\n        for (auto& el : MAT) {\n            if (!std::isfinite(el) || el < 0.F) {\n                m_resource->error(HYPRLAND_CTM_CONTROL_MANAGER_V1_ERROR_INVALID_MATRIX, \"a matrix component was invalid\");\n                return;\n            }\n        }\n\n        m_ctms[PMONITOR->m_name] = MAT;\n\n        LOGM(Log::DEBUG, \"CTM set for output {}: {}\", PMONITOR->m_name, m_ctms.at(PMONITOR->m_name).toString());\n    });\n\n    m_resource->setCommit([this](CHyprlandCtmControlManagerV1* r) {\n        if (m_blocked)\n            return;\n\n        LOGM(Log::DEBUG, \"Committing ctms to outputs\");\n\n        for (auto& m : g_pCompositor->m_monitors) {\n            if (!m_ctms.contains(m->m_name)) {\n                PROTO::ctm->setCTM(m, Mat3x3::identity());\n                continue;\n            }\n\n            PROTO::ctm->setCTM(m, m_ctms.at(m->m_name));\n        }\n    });\n}\n\nvoid CHyprlandCTMControlResource::block() {\n    m_blocked = true;\n\n    if (m_resource->version() >= 2)\n        m_resource->sendBlocked();\n}\n\nCHyprlandCTMControlResource::~CHyprlandCTMControlResource() {\n    if (m_blocked)\n        return;\n\n    for (auto& m : g_pCompositor->m_monitors) {\n        PROTO::ctm->setCTM(m, Mat3x3::identity());\n    }\n}\n\nbool CHyprlandCTMControlResource::good() {\n    return m_resource->resource();\n}\n\nCHyprlandCTMControlProtocol::CHyprlandCTMControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CHyprlandCTMControlProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto& RESOURCE = m_managers.emplace_back(makeUnique<CHyprlandCTMControlResource>(makeUnique<CHyprlandCtmControlManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    if (m_manager)\n        RESOURCE->block();\n    else\n        m_manager = RESOURCE;\n\n    LOGM(Log::DEBUG, \"New CTM Manager at 0x{:x}\", (uintptr_t)RESOURCE.get());\n}\n\nvoid CHyprlandCTMControlProtocol::destroyResource(CHyprlandCTMControlResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n\nbool CHyprlandCTMControlProtocol::isCTMAnimationEnabled() {\n    static auto PENABLEANIM = CConfigValue<Hyprlang::INT>(\"render:ctm_animation\");\n\n    if (*PENABLEANIM == 2 /* auto */) {\n        if (!g_pHyprRenderer->isNvidia())\n            return true;\n        // CTM animations are bugged on versions below.\n        return isNvidiaDriverVersionAtLeast(575);\n    }\n    return *PENABLEANIM;\n}\n\nCHyprlandCTMControlProtocol::SCTMData::SCTMData() {\n    g_pAnimationManager->createAnimation(0.f, progress, g_pConfigManager->getAnimationPropertyConfig(\"__internal_fadeCTM\"), AVARDAMAGE_NONE);\n}\n\nvoid CHyprlandCTMControlProtocol::setCTM(PHLMONITOR monitor, const Mat3x3& ctm) {\n    if (!isCTMAnimationEnabled()) {\n        monitor->setCTM(ctm);\n        return;\n    }\n\n    std::erase_if(m_ctmDatas, [](const auto& el) { return !el.first; });\n\n    if (!m_ctmDatas.contains(monitor))\n        m_ctmDatas[monitor] = makeUnique<SCTMData>();\n\n    auto& data = m_ctmDatas.at(monitor);\n\n    data->ctmFrom = data->ctmTo;\n    data->ctmTo   = ctm;\n\n    data->progress->setValueAndWarp(0.F);\n    *data->progress = 1.F;\n\n    monitor->setCTM(data->ctmFrom);\n\n    data->progress->setUpdateCallback([monitor = PHLMONITORREF{monitor}, this](auto) {\n        if (!monitor || !m_ctmDatas.contains(monitor))\n            return;\n        auto&                data     = m_ctmDatas.at(monitor);\n        const auto           from     = data->ctmFrom.getMatrix();\n        const auto           to       = data->ctmTo.getMatrix();\n        const auto           PROGRESS = data->progress->getPercent();\n\n        static const auto    lerp = [](const float one, const float two, const float progress) -> float { return one + (two - one) * progress; };\n\n        std::array<float, 9> mtx;\n        for (size_t i = 0; i < 9; ++i) {\n            mtx[i] = lerp(from[i], to[i], PROGRESS);\n        }\n\n        monitor->setCTM(mtx);\n    });\n\n    data->progress->setCallbackOnEnd([monitor = PHLMONITORREF{monitor}, this](auto) {\n        if (!monitor || !m_ctmDatas.contains(monitor)) {\n            if (monitor)\n                monitor->setCTM(Mat3x3::identity());\n            return;\n        }\n        auto& data = m_ctmDatas.at(monitor);\n        monitor->setCTM(data->ctmTo);\n    });\n}\n"
  },
  {
    "path": "src/protocols/CTMControl.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"hyprland-ctm-control-v1.hpp\"\n#include <unordered_map>\n#include <map>\n#include \"../helpers/AnimatedVariable.hpp\"\n\nclass CMonitor;\n\nclass CHyprlandCTMControlResource {\n  public:\n    CHyprlandCTMControlResource(UP<CHyprlandCtmControlManagerV1>&& resource_);\n    ~CHyprlandCTMControlResource();\n\n    bool good();\n    void block();\n\n  private:\n    UP<CHyprlandCtmControlManagerV1>        m_resource;\n\n    std::unordered_map<std::string, Mat3x3> m_ctms;\n    bool                                    m_blocked = false;\n};\n\nclass CHyprlandCTMControlProtocol : public IWaylandProtocol {\n  public:\n    CHyprlandCTMControlProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CHyprlandCTMControlResource* resource);\n\n    void setCTM(PHLMONITOR monitor, const Mat3x3& ctm);\n    bool isCTMAnimationEnabled();\n\n    //\n    std::vector<UP<CHyprlandCTMControlResource>> m_managers;\n    WP<CHyprlandCTMControlResource>              m_manager;\n\n    //\n    struct SCTMData {\n        SCTMData();\n        Mat3x3            ctmFrom = Mat3x3::identity(), ctmTo = Mat3x3::identity();\n        PHLANIMVAR<float> progress;\n    };\n    std::map<PHLMONITORREF, UP<SCTMData>> m_ctmDatas;\n\n    friend class CHyprlandCTMControlResource;\n};\n\nnamespace PROTO {\n    inline UP<CHyprlandCTMControlProtocol> ctm;\n};\n"
  },
  {
    "path": "src/protocols/ColorManagement.cpp",
    "content": "#include \"ColorManagement.hpp\"\n#include \"Compositor.hpp\"\n#include \"color-management-v1.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"core/Output.hpp\"\n#include \"../helpers/cm/ColorManagement.hpp\"\n#include <cstdint>\n\nusing namespace NColorManagement;\n\nconst auto PRIMARIES_SCALE = 1000000.0f;\n\nCColorManager::CColorManager(SP<CWpColorManagerV1> resource) : m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC);\n    m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES);\n    m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES);\n    m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB);\n    m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES);\n    m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME);\n\n    if (PROTO::colorManagement->m_debug) {\n        m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4);\n        m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER);\n    }\n\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_SRGB);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_BT2020);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_PAL);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_NTSC);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB);\n    m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ);\n\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB);\n    m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428);\n\n    m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);\n    if (PROTO::colorManagement->m_debug) {\n        m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE);\n        m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION);\n        m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE);\n        m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC);\n    }\n\n    m_resource->setDestroy([](CWpColorManagerV1* r) { LOGM(Log::TRACE, \"Destroy WP_color_manager at {:x} (generated default)\", (uintptr_t)r); });\n    m_resource->setGetOutput([](CWpColorManagerV1* r, uint32_t id, wl_resource* output) {\n        LOGM(Log::TRACE, \"Get output for id={}, output={}\", id, (uintptr_t)output);\n\n        const auto OUTPUTRESOURCE = CWLOutputResource::fromResource(output);\n\n        const auto RESOURCE = PROTO::colorManagement->m_outputs.emplace_back(\n            makeShared<CColorManagementOutput>(makeShared<CWpColorManagementOutputV1>(r->client(), r->version(), id), OUTPUTRESOURCE));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_outputs.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n    m_resource->setGetSurface([](CWpColorManagerV1* r, uint32_t id, wl_resource* surface) {\n        LOGM(Log::TRACE, \"Get surface for id={}, surface={}\", id, (uintptr_t)surface);\n        auto SURF = CWLSurfaceResource::fromResource(surface);\n\n        if (!SURF) {\n            LOGM(Log::ERR, \"No surface for resource {}\", (uintptr_t)surface);\n            r->error(-1, \"Invalid surface (2)\");\n            return;\n        }\n\n        if (SURF->m_colorManagement) {\n            r->error(WP_COLOR_MANAGER_V1_ERROR_SURFACE_EXISTS, \"CM Surface already exists\");\n            return;\n        }\n\n        const auto RESOURCE =\n            PROTO::colorManagement->m_surfaces.emplace_back(makeShared<CColorManagementSurface>(makeShared<CWpColorManagementSurfaceV1>(r->client(), r->version(), id), SURF));\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_surfaces.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        SURF->m_colorManagement = RESOURCE;\n    });\n    m_resource->setGetSurfaceFeedback([](CWpColorManagerV1* r, uint32_t id, wl_resource* surface) {\n        LOGM(Log::TRACE, \"Get feedback surface for id={}, surface={}\", id, (uintptr_t)surface);\n        auto SURF = CWLSurfaceResource::fromResource(surface);\n\n        if (!SURF) {\n            LOGM(Log::ERR, \"No surface for resource {}\", (uintptr_t)surface);\n            r->error(-1, \"Invalid surface (2)\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::colorManagement->m_feedbackSurfaces.emplace_back(\n            makeShared<CColorManagementFeedbackSurface>(makeShared<CWpColorManagementSurfaceFeedbackV1>(r->client(), r->version(), id), SURF));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_feedbackSurfaces.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n    m_resource->setCreateIccCreator([](CWpColorManagerV1* r, uint32_t id) {\n        LOGM(Log::WARN, \"New ICC creator for id={} (unsupported)\", id);\n        if (!PROTO::colorManagement->m_debug) {\n            r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, \"ICC profiles are not supported\");\n            return;\n        }\n\n        const auto RESOURCE =\n            PROTO::colorManagement->m_iccCreators.emplace_back(makeShared<CColorManagementIccCreator>(makeShared<CWpImageDescriptionCreatorIccV1>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_iccCreators.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n    m_resource->setCreateParametricCreator([](CWpColorManagerV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"New parametric creator for id={}\", id);\n\n        const auto RESOURCE = PROTO::colorManagement->m_parametricCreators.emplace_back(\n            makeShared<CColorManagementParametricCreator>(makeShared<CWpImageDescriptionCreatorParamsV1>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_parametricCreators.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n    m_resource->setCreateWindowsScrgb([](CWpColorManagerV1* r, uint32_t id) {\n        LOGM(Log::WARN, \"New Windows scRGB description id={}\", id);\n\n        const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back(\n            makeShared<CColorManagementImageDescription>(makeShared<CWpImageDescriptionV1>(r->client(), r->version(), id), false));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_imageDescriptions.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self     = RESOURCE;\n        RESOURCE->m_settings = SCRGB_IMAGE_DESCRIPTION;\n\n        RESOURCE->resource()->sendReady(RESOURCE->m_settings->id());\n    });\n\n    m_resource->setOnDestroy([this](CWpColorManagerV1* r) { PROTO::colorManagement->destroyResource(this); });\n\n    m_resource->sendDone();\n}\n\nbool CColorManager::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManager::client() {\n    return m_resource->client();\n}\n\nCColorManagementOutput::CColorManagementOutput(SP<CWpColorManagementOutputV1> resource, WP<CWLOutputResource> output) : m_resource(resource), m_output(output) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CWpColorManagementOutputV1* r) { PROTO::colorManagement->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpColorManagementOutputV1* r) { PROTO::colorManagement->destroyResource(this); });\n\n    m_resource->setGetImageDescription([this](CWpColorManagementOutputV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"Get image description for output={}, id={}\", (uintptr_t)r, id);\n\n        if (m_imageDescription.valid())\n            PROTO::colorManagement->destroyResource(m_imageDescription.get());\n\n        const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back(\n            makeShared<CColorManagementImageDescription>(makeShared<CWpImageDescriptionV1>(r->client(), r->version(), id), true));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_imageDescriptions.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n        if (!m_output || !m_output->m_monitor.valid())\n            RESOURCE->m_resource->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_NO_OUTPUT, \"No output\");\n        else {\n            RESOURCE->m_settings = m_output->m_monitor->m_imageDescription;\n            RESOURCE->m_resource->sendReady(RESOURCE->m_settings->id());\n        }\n    });\n}\n\nbool CColorManagementOutput::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManagementOutput::client() {\n    return m_client;\n}\n\nCColorManagementSurface::CColorManagementSurface(SP<CWpColorManagementSurfaceV1> resource, SP<CWLSurfaceResource> surface_) : m_surface(surface_), m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client           = m_resource->client();\n    m_imageDescription = DEFAULT_IMAGE_DESCRIPTION;\n\n    m_resource->setDestroy([this](CWpColorManagementSurfaceV1* r) {\n        LOGM(Log::TRACE, \"Destroy wp cm surface {}\", (uintptr_t)m_surface);\n        PROTO::colorManagement->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CWpColorManagementSurfaceV1* r) {\n        LOGM(Log::TRACE, \"Destroy wp cm surface {}\", (uintptr_t)m_surface);\n        PROTO::colorManagement->destroyResource(this);\n    });\n\n    m_resource->setSetImageDescription([this](CWpColorManagementSurfaceV1* r, wl_resource* image_description, uint32_t render_intent) {\n        LOGM(Log::TRACE, \"Set image description for surface={}, desc={}, intent={}\", (uintptr_t)r, (uintptr_t)image_description, render_intent);\n\n        const auto PO = sc<CWpImageDescriptionV1*>(wl_resource_get_user_data(image_description));\n        if (!PO) { // FIXME check validity\n            r->error(WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION, \"Image description creation failed\");\n            return;\n        }\n        if (render_intent != WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL) {\n            r->error(WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_RENDER_INTENT, \"Unsupported render intent\");\n            return;\n        }\n\n        const auto imageDescription =\n            std::ranges::find_if(PROTO::colorManagement->m_imageDescriptions, [&](const auto& other) { return other->resource()->resource() == image_description; });\n        if (imageDescription == PROTO::colorManagement->m_imageDescriptions.end()) {\n            r->error(WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION, \"Image description not found\");\n            return;\n        }\n\n        setHasImageDescription(true);\n        m_imageDescription = imageDescription->get()->m_settings;\n    });\n    m_resource->setUnsetImageDescription([this](CWpColorManagementSurfaceV1* r) {\n        LOGM(Log::TRACE, \"Unset image description for surface={}\", (uintptr_t)r);\n        m_imageDescription = DEFAULT_IMAGE_DESCRIPTION;\n        setHasImageDescription(false);\n    });\n}\n\nbool CColorManagementSurface::good() {\n    return m_resource && m_resource->resource();\n}\n\nwl_client* CColorManagementSurface::client() {\n    return m_client;\n}\n\nconst SImageDescription& CColorManagementSurface::imageDescription() {\n    if (!hasImageDescription())\n        LOGM(Log::WARN, \"Reading imageDescription while none set. Returns default or empty values\");\n\n    return m_imageDescription->value();\n}\n\nbool CColorManagementSurface::hasImageDescription() {\n    return m_hasImageDescription;\n}\n\nvoid CColorManagementSurface::setHasImageDescription(bool has) {\n    m_hasImageDescription = has;\n    m_needsNewMetadata    = true;\n}\n\nconst hdr_output_metadata& CColorManagementSurface::hdrMetadata() {\n    return m_hdrMetadata;\n}\n\nvoid CColorManagementSurface::setHDRMetadata(const hdr_output_metadata& metadata) {\n    m_hdrMetadata          = metadata;\n    m_lastImageDescription = m_imageDescription;\n    m_needsNewMetadata     = false;\n}\n\nbool CColorManagementSurface::needsHdrMetadataUpdate() {\n    if (!m_needsNewMetadata)\n        return false;\n    if (m_imageDescription == m_lastImageDescription)\n        m_needsNewMetadata = false;\n    return m_needsNewMetadata;\n}\n\nbool CColorManagementSurface::isHDR() {\n    return m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_HLG ||\n        isWindowsScRGB();\n}\n\nbool CColorManagementSurface::isWindowsScRGB() {\n    return m_imageDescription->value().windowsScRGB ||\n        // autodetect scRGB, might be incorrect\n        (m_imageDescription->value().primariesNamed == CM_PRIMARIES_SRGB && m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR);\n}\n\nCColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SP<CWpColorManagementSurfaceFeedbackV1> resource, SP<CWLSurfaceResource> surface_) :\n    m_surface(surface_), m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CWpColorManagementSurfaceFeedbackV1* r) {\n        LOGM(Log::TRACE, \"Destroy wp cm feedback surface {}\", (uintptr_t)m_surface);\n        PROTO::colorManagement->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CWpColorManagementSurfaceFeedbackV1* r) {\n        LOGM(Log::TRACE, \"Destroy wp cm feedback surface {}\", (uintptr_t)m_surface);\n        PROTO::colorManagement->destroyResource(this);\n    });\n\n    m_resource->setGetPreferred([this](CWpColorManagementSurfaceFeedbackV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"Get preferred for id {}\", id);\n\n        if (m_surface.expired()) {\n            r->error(WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT, \"Surface is inert\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back(\n            makeShared<CColorManagementImageDescription>(makeShared<CWpImageDescriptionV1>(r->client(), r->version(), id), true));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_imageDescriptions.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self     = RESOURCE;\n        RESOURCE->m_settings = m_surface->getPreferredImageDescription();\n\n        RESOURCE->resource()->sendReady(RESOURCE->m_settings->id());\n    });\n\n    m_resource->setGetPreferredParametric([this](CWpColorManagementSurfaceFeedbackV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"Get preferred for id {}\", id);\n\n        if (m_surface.expired()) {\n            r->error(WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT, \"Surface is inert\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back(\n            makeShared<CColorManagementImageDescription>(makeShared<CWpImageDescriptionV1>(r->client(), r->version(), id), true));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_imageDescriptions.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self     = RESOURCE;\n        RESOURCE->m_settings = m_surface->getPreferredImageDescription();\n        m_currentPreferredId = RESOURCE->m_settings->id();\n\n        RESOURCE->resource()->sendReady(m_currentPreferredId);\n    });\n\n    m_listeners.enter = m_surface->m_events.enter.listen([this](const auto& monitor) { onPreferredChanged(); });\n    m_listeners.leave = m_surface->m_events.leave.listen([this](const auto& monitor) { onPreferredChanged(); });\n}\n\nvoid CColorManagementFeedbackSurface::onPreferredChanged() {\n    if (m_surface->m_enteredOutputs.size() == 1) {\n        const auto newId = m_surface->getPreferredImageDescription()->id();\n        if (m_currentPreferredId != newId)\n            m_resource->sendPreferredChanged(newId);\n    }\n}\n\nbool CColorManagementFeedbackSurface::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManagementFeedbackSurface::client() {\n    return m_client;\n}\n\nCColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCreatorIccV1> resource) : m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n    //\n    m_client = m_resource->client();\n\n    m_resource->setOnDestroy([this](CWpImageDescriptionCreatorIccV1* r) { PROTO::colorManagement->destroyResource(this); });\n\n    m_resource->setCreate([this](CWpImageDescriptionCreatorIccV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"Create image description from icc for id {}\", id);\n\n        // FIXME actually check completeness\n        if (m_icc.fd < 0 || !m_icc.length) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, \"Missing required settings\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back(\n            makeShared<CColorManagementImageDescription>(makeShared<CWpImageDescriptionV1>(r->client(), r->version(), id), false));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_imageDescriptions.pop_back();\n            return;\n        }\n\n        LOGM(Log::ERR, \"FIXME: Parse icc file {}({},{}) for id {}\", m_icc.fd, m_icc.offset, m_icc.length, id);\n\n        // FIXME actually check support\n        if (m_icc.fd < 0 || !m_icc.length) {\n            RESOURCE->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, \"unsupported\");\n            return;\n        }\n\n        RESOURCE->m_self     = RESOURCE;\n        RESOURCE->m_settings = CImageDescription::from(m_settings);\n        RESOURCE->resource()->sendReady(RESOURCE->m_settings->id());\n\n        PROTO::colorManagement->destroyResource(this);\n    });\n\n    m_resource->setSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) {\n        m_icc.fd     = fd;\n        m_icc.offset = offset;\n        m_icc.length = length;\n    });\n}\n\nbool CColorManagementIccCreator::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManagementIccCreator::client() {\n    return m_client;\n}\n\nCColorManagementParametricCreator::CColorManagementParametricCreator(SP<CWpImageDescriptionCreatorParamsV1> resource) : m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n    //\n    m_client = m_resource->client();\n\n    m_resource->setOnDestroy([this](CWpImageDescriptionCreatorParamsV1* r) { PROTO::colorManagement->destroyResource(this); });\n\n    m_resource->setCreate([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"Create image description from params for id {}\", id);\n\n        // FIXME actually check completeness\n        if (!m_valuesSet) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, \"Missing required settings\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back(\n            makeShared<CColorManagementImageDescription>(makeShared<CWpImageDescriptionV1>(r->client(), r->version(), id), false));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::colorManagement->m_imageDescriptions.pop_back();\n            return;\n        }\n\n        // FIXME actually check support\n        if (!m_valuesSet) {\n            RESOURCE->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, \"unsupported\");\n            return;\n        }\n\n        if ((m_valuesSet & PC_TF) && !(m_valuesSet & PC_LUMINANCES)) {\n            m_settings.luminances = {\n                .min       = m_settings.getTFMinLuminance(),\n                .max       = m_settings.getTFMaxLuminance(),\n                .reference = m_settings.getTFRefLuminance(),\n            };\n        }\n\n        RESOURCE->m_self     = RESOURCE;\n        RESOURCE->m_settings = CImageDescription::from(m_settings);\n        RESOURCE->resource()->sendReady(RESOURCE->m_settings->id());\n\n        PROTO::colorManagement->destroyResource(this);\n    });\n    m_resource->setSetTfNamed([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t tf) {\n        LOGM(Log::TRACE, \"Set image description transfer function to {}\", tf);\n        if (m_valuesSet & PC_TF) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Transfer function already set\");\n            return;\n        }\n\n        switch (tf) {\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB: break;\n            case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428: break;\n            default: r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF, \"Unsupported transfer function\"); return;\n        }\n\n        m_settings.transferFunction = convertTransferFunction(sc<wpColorManagerV1TransferFunction>(tf));\n        m_valuesSet |= PC_TF;\n    });\n    m_resource->setSetTfPower([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t eexp) {\n        LOGM(Log::TRACE, \"Set image description tf power to {}\", eexp);\n        if (m_valuesSet & PC_TF_POWER) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Transfer function power already set\");\n            return;\n        }\n        if (!PROTO::colorManagement->m_debug) {\n            r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, \"TF power is not supported\");\n            return;\n        }\n        m_settings.transferFunctionPower = eexp / 10000.0f;\n        if (m_settings.transferFunctionPower < 1.0 || m_settings.transferFunctionPower > 10.0) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF, \"Power should be between 1.0 and 10.0\");\n            return;\n        }\n        m_valuesSet |= PC_TF_POWER;\n    });\n    m_resource->setSetPrimariesNamed([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t primaries) {\n        LOGM(Log::TRACE, \"Set image description primaries by name {}\", primaries);\n        if (m_valuesSet & PC_PRIMARIES) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Primaries already set\");\n            return;\n        }\n\n        switch (primaries) {\n            case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_PAL:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_NTSC:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3:\n            case WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB: break;\n            default:\n                if (!PROTO::colorManagement->m_debug) {\n                    r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED, \"Unsupported primaries\");\n                    return;\n                }\n        }\n\n        m_settings.primariesNameSet = true;\n        m_settings.primariesNamed   = convertPrimaries(sc<wpColorManagerV1Primaries>(primaries));\n        m_settings.primaries        = getPrimaries(m_settings.primariesNamed);\n        m_valuesSet |= PC_PRIMARIES;\n    });\n    m_resource->setSetPrimaries(\n        [this](CWpImageDescriptionCreatorParamsV1* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) {\n            LOGM(Log::TRACE, \"Set image description primaries by values r:{},{} g:{},{} b:{},{} w:{},{}\", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y);\n            if (m_valuesSet & PC_PRIMARIES) {\n                r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Primaries already set\");\n                return;\n            }\n            m_settings.primariesNameSet = false;\n            m_settings.primaries        = SPCPRimaries{.red   = {.x = r_x / PRIMARIES_SCALE, .y = r_y / PRIMARIES_SCALE},\n                                                       .green = {.x = g_x / PRIMARIES_SCALE, .y = g_y / PRIMARIES_SCALE},\n                                                       .blue  = {.x = b_x / PRIMARIES_SCALE, .y = b_y / PRIMARIES_SCALE},\n                                                       .white = {.x = w_x / PRIMARIES_SCALE, .y = w_y / PRIMARIES_SCALE}};\n            m_valuesSet |= PC_PRIMARIES;\n        });\n    m_resource->setSetLuminances([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) {\n        auto min = min_lum / 10000.0f;\n        LOGM(Log::TRACE, \"Set image description luminances to {} - {} ({})\", min, max_lum, reference_lum);\n        if (m_valuesSet & PC_LUMINANCES) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Luminances already set\");\n            return;\n        }\n        if (max_lum <= min || reference_lum <= min) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, \"Invalid luminances\");\n            return;\n        }\n        m_settings.luminances = SImageDescription::SPCLuminances{.min = min, .max = max_lum, .reference = reference_lum};\n        m_valuesSet |= PC_LUMINANCES;\n    });\n    m_resource->setSetMasteringDisplayPrimaries(\n        [this](CWpImageDescriptionCreatorParamsV1* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) {\n            LOGM(Log::TRACE, \"Set image description mastering primaries by values r:{},{} g:{},{} b:{},{} w:{},{}\", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y);\n            if (m_valuesSet & PC_MASTERING_PRIMARIES) {\n                r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Mastering primaries already set\");\n                return;\n            }\n\n            m_settings.masteringPrimaries = SPCPRimaries{.red   = {.x = r_x / PRIMARIES_SCALE, .y = r_y / PRIMARIES_SCALE},\n                                                         .green = {.x = g_x / PRIMARIES_SCALE, .y = g_y / PRIMARIES_SCALE},\n                                                         .blue  = {.x = b_x / PRIMARIES_SCALE, .y = b_y / PRIMARIES_SCALE},\n                                                         .white = {.x = w_x / PRIMARIES_SCALE, .y = w_y / PRIMARIES_SCALE}};\n            m_valuesSet |= PC_MASTERING_PRIMARIES;\n\n            // FIXME:\n            // If a compositor additionally supports target color volume exceeding the primary color volume, it must advertise wp_color_manager_v1.feature.extended_target_volume.\n            // If a client uses target color volume exceeding the primary color volume and the compositor does not support it, the result is implementation defined.\n            // Compositors are recommended to detect this case and fail the image description gracefully, but it may as well result in color artifacts.\n        });\n    m_resource->setSetMasteringLuminance([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t min_lum, uint32_t max_lum) {\n        auto min = min_lum / 10000.0f;\n        LOGM(Log::TRACE, \"Set image description mastering luminances to {} - {}\", min, max_lum);\n        // if (valuesSet & PC_MASTERING_LUMINANCES) {\n        //     r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Mastering luminances already set\");\n        //     return;\n        // }\n        if (min > 0 && max_lum > 0 && max_lum <= min) {\n            r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, \"Invalid luminances\");\n            return;\n        }\n\n        m_settings.masteringLuminances = SImageDescription::SPCMasteringLuminances{.min = min, .max = max_lum};\n        m_valuesSet |= PC_MASTERING_LUMINANCES;\n    });\n    m_resource->setSetMaxCll([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t max_cll) {\n        LOGM(Log::TRACE, \"Set image description max content light level to {}\", max_cll);\n        // if (valuesSet & PC_CLL) {\n        //     r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Max CLL already set\");\n        //     return;\n        // }\n        m_settings.maxCLL = max_cll;\n        m_valuesSet |= PC_CLL;\n    });\n    m_resource->setSetMaxFall([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t max_fall) {\n        LOGM(Log::TRACE, \"Set image description max frame-average light level to {}\", max_fall);\n        // if (valuesSet & PC_FALL) {\n        //     r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \"Max FALL already set\");\n        //     return;\n        // }\n        m_settings.maxFALL = max_fall;\n        m_valuesSet |= PC_FALL;\n    });\n}\n\nbool CColorManagementParametricCreator::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManagementParametricCreator::client() {\n    return m_client;\n}\n\nCColorManagementImageDescription::CColorManagementImageDescription(SP<CWpImageDescriptionV1> resource, bool allowGetInformation) :\n    m_resource(resource), m_allowGetInformation(allowGetInformation) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CWpImageDescriptionV1* r) { PROTO::colorManagement->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpImageDescriptionV1* r) { PROTO::colorManagement->destroyResource(this); });\n\n    m_resource->setGetInformation([this](CWpImageDescriptionV1* r, uint32_t id) {\n        LOGM(Log::TRACE, \"Get image information for image={}, id={}\", (uintptr_t)r, id);\n        if (!m_allowGetInformation) {\n            r->error(WP_IMAGE_DESCRIPTION_V1_ERROR_NO_INFORMATION, \"Image descriptions doesn't allow get_information request\");\n            return;\n        }\n\n        auto RESOURCE = makeShared<CColorManagementImageDescriptionInfo>(makeShared<CWpImageDescriptionInfoV1>(r->client(), r->version(), id), m_settings->value());\n\n        if UNLIKELY (!RESOURCE->good())\n            r->noMemory();\n\n        // CColorManagementImageDescriptionInfo should send everything in the constructor and be ready for destroying at this point\n        RESOURCE.reset();\n    });\n}\n\nbool CColorManagementImageDescription::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManagementImageDescription::client() {\n    return m_client;\n}\n\nSP<CWpImageDescriptionV1> CColorManagementImageDescription::resource() {\n    return m_resource;\n}\n\nCColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP<CWpImageDescriptionInfoV1> resource, const SImageDescription& settings_) :\n    m_resource(resource), m_settings(settings_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    const auto toProto = [](float value) { return sc<int32_t>(std::round(value * PRIMARIES_SCALE)); };\n\n    // FIXME:\n    // if (m_icc.fd >= 0)\n    //    m_resource->sendIccFile(m_icc.fd, m_icc.length);\n\n    // send preferred client paramateres\n    m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x),\n                              toProto(m_settings.primaries.green.y), toProto(m_settings.primaries.blue.x), toProto(m_settings.primaries.blue.y),\n                              toProto(m_settings.primaries.white.x), toProto(m_settings.primaries.white.y));\n\n    if (m_settings.primariesNameSet)\n        m_resource->sendPrimariesNamed(m_settings.primariesNamed);\n\n    m_resource->sendTfNamed(m_settings.transferFunction);\n\n    if (m_settings.transferFunctionPower != 1.0f)\n        m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000));\n\n    m_resource->sendLuminances(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max, m_settings.luminances.reference);\n\n    const auto& targetPrimaries = (                                                                                               //\n                                      m_settings.masteringPrimaries.red.x != 0 || m_settings.masteringPrimaries.red.y != 0 ||     //\n                                      m_settings.masteringPrimaries.green.x != 0 || m_settings.masteringPrimaries.green.y != 0 || //\n                                      m_settings.masteringPrimaries.blue.x != 0 || m_settings.masteringPrimaries.blue.y != 0) ?\n        m_settings.masteringPrimaries :\n        m_settings.primaries;\n\n    m_resource->sendTargetPrimaries(                                        //\n        toProto(targetPrimaries.red.x), toProto(targetPrimaries.red.y),     //\n        toProto(targetPrimaries.green.x), toProto(targetPrimaries.green.y), //\n        toProto(targetPrimaries.blue.x), toProto(targetPrimaries.blue.y),   //\n        toProto(targetPrimaries.white.x), toProto(targetPrimaries.white.y));\n\n    if (m_settings.masteringLuminances.max > 0)\n        m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max);\n    else\n        m_resource->sendTargetLuminance(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max);\n\n    if (m_settings.maxCLL > 0 || m_settings.maxFALL > 0) {\n        m_resource->sendTargetMaxCll(m_settings.maxCLL);\n        m_resource->sendTargetMaxFall(m_settings.maxFALL);\n    }\n\n    m_resource->sendDone();\n}\n\nbool CColorManagementImageDescriptionInfo::good() {\n    return m_resource->resource();\n}\n\nwl_client* CColorManagementImageDescriptionInfo::client() {\n    return m_client;\n}\n\nCColorManagementProtocol::CColorManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name, bool debug) :\n    IWaylandProtocol(iface, ver, name), m_debug(debug) {\n    ;\n}\n\nvoid CColorManagementProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CColorManager>(makeShared<CWpColorManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    LOGM(Log::TRACE, \"New WP_color_manager at {:x}\", (uintptr_t)RESOURCE.get());\n}\n\nvoid CColorManagementProtocol::onImagePreferredChanged(uint32_t preferredId) {\n    for (auto const& feedback : m_feedbackSurfaces) {\n        feedback->m_resource->sendPreferredChanged(preferredId);\n    }\n}\n\nvoid CColorManagementProtocol::onMonitorImageDescriptionChanged(WP<CMonitor> monitor) {\n    for (auto const& output : m_outputs) {\n        if (output->m_output && output->m_output->m_monitor == monitor)\n            output->m_resource->sendImageDescriptionChanged();\n    }\n    // recheck feedbacks\n    for (auto const& feedback : m_feedbackSurfaces)\n        feedback->onPreferredChanged();\n}\n\nbool CColorManagementProtocol::isClientCMAware(wl_client* client) {\n    return std::ranges::any_of(m_managers, [client](const auto& m) { return m->client() == client; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManager* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManagementOutput* resource) {\n    std::erase_if(m_outputs, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManagementSurface* resource) {\n    std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManagementFeedbackSurface* resource) {\n    std::erase_if(m_feedbackSurfaces, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManagementIccCreator* resource) {\n    std::erase_if(m_iccCreators, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManagementParametricCreator* resource) {\n    std::erase_if(m_parametricCreators, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CColorManagementProtocol::destroyResource(CColorManagementImageDescription* resource) {\n    std::erase_if(m_imageDescriptions, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/ColorManagement.hpp",
    "content": "#pragma once\n\n#include <drm_mode.h>\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"color-management-v1.hpp\"\n#include \"../helpers/cm/ColorManagement.hpp\"\n\nclass CColorManager;\nclass CColorManagementOutput;\nclass CColorManagementImageDescription;\nclass CColorManagementProtocol;\n\nclass CColorManager {\n  public:\n    CColorManager(SP<CWpColorManagerV1> resource);\n\n    bool       good();\n    wl_client* client();\n\n  private:\n    SP<CWpColorManagerV1> m_resource;\n};\n\nclass CColorManagementOutput {\n  public:\n    CColorManagementOutput(SP<CWpColorManagementOutputV1> resource, WP<CWLOutputResource> output);\n\n    bool                                 good();\n    wl_client*                           client();\n\n    WP<CColorManagementOutput>           m_self;\n    WP<CColorManagementImageDescription> m_imageDescription;\n\n  private:\n    SP<CWpColorManagementOutputV1> m_resource;\n    wl_client*                     m_client = nullptr;\n    WP<CWLOutputResource>          m_output;\n\n    friend class CColorManagementProtocol;\n    friend class CColorManagementImageDescription;\n};\n\nclass CColorManagementSurface {\n  public:\n    CColorManagementSurface(SP<CWpColorManagementSurfaceV1> resource, SP<CWLSurfaceResource> surface_);\n\n    bool                                       good();\n    wl_client*                                 client();\n\n    WP<CColorManagementSurface>                m_self;\n    WP<CWLSurfaceResource>                     m_surface;\n\n    const NColorManagement::SImageDescription& imageDescription();\n    bool                                       hasImageDescription();\n    void                                       setHasImageDescription(bool has);\n    const hdr_output_metadata&                 hdrMetadata();\n    void                                       setHDRMetadata(const hdr_output_metadata& metadata);\n    bool                                       needsHdrMetadataUpdate();\n    bool                                       isHDR();\n    bool                                       isWindowsScRGB();\n\n  private:\n    SP<CWpColorManagementSurfaceV1>     m_resource;\n    wl_client*                          m_client = nullptr;\n    NColorManagement::PImageDescription m_imageDescription;\n    NColorManagement::PImageDescription m_lastImageDescription;\n    bool                                m_hasImageDescription = false;\n    bool                                m_needsNewMetadata    = false;\n    hdr_output_metadata                 m_hdrMetadata;\n};\n\nclass CColorManagementFeedbackSurface {\n  public:\n    CColorManagementFeedbackSurface(SP<CWpColorManagementSurfaceFeedbackV1> resource, SP<CWLSurfaceResource> surface_);\n\n    bool                                good();\n    wl_client*                          client();\n\n    WP<CColorManagementFeedbackSurface> m_self;\n    WP<CWLSurfaceResource>              m_surface;\n\n  private:\n    SP<CWpColorManagementSurfaceFeedbackV1> m_resource;\n    wl_client*                              m_client = nullptr;\n\n    uint32_t                                m_currentPreferredId = 0;\n\n    struct {\n        CHyprSignalListener enter;\n        CHyprSignalListener leave;\n    } m_listeners;\n\n    void onPreferredChanged();\n\n    friend class CColorManagementProtocol;\n};\n\nclass CColorManagementIccCreator {\n  public:\n    CColorManagementIccCreator(SP<CWpImageDescriptionCreatorIccV1> resource);\n\n    bool                                good();\n    wl_client*                          client();\n\n    WP<CColorManagementIccCreator>      m_self;\n\n    NColorManagement::SImageDescription m_settings;\n    struct SIccFile {\n        int      fd     = -1;\n        uint32_t length = 0;\n        uint32_t offset = 0;\n        bool     operator==(const SIccFile& i2) const {\n            return fd == i2.fd;\n        }\n    } m_icc;\n\n  private:\n    SP<CWpImageDescriptionCreatorIccV1> m_resource;\n    wl_client*                          m_client = nullptr;\n};\n\nclass CColorManagementParametricCreator {\n  public:\n    CColorManagementParametricCreator(SP<CWpImageDescriptionCreatorParamsV1> resource);\n\n    bool                                  good();\n    wl_client*                            client();\n\n    WP<CColorManagementParametricCreator> m_self;\n\n    NColorManagement::SImageDescription   m_settings;\n\n  private:\n    enum eValuesSet : uint32_t { // NOLINT\n        PC_TF                   = (1 << 0),\n        PC_TF_POWER             = (1 << 1),\n        PC_PRIMARIES            = (1 << 2),\n        PC_LUMINANCES           = (1 << 3),\n        PC_MASTERING_PRIMARIES  = (1 << 4),\n        PC_MASTERING_LUMINANCES = (1 << 5),\n        PC_CLL                  = (1 << 6),\n        PC_FALL                 = (1 << 7),\n    };\n\n    SP<CWpImageDescriptionCreatorParamsV1> m_resource;\n    wl_client*                             m_client    = nullptr;\n    uint32_t                               m_valuesSet = 0; // enum eValuesSet\n};\n\nclass CColorManagementImageDescription {\n  public:\n    CColorManagementImageDescription(SP<CWpImageDescriptionV1> resource, bool allowGetInformation);\n\n    bool                                 good();\n    wl_client*                           client();\n    SP<CWpImageDescriptionV1>            resource();\n\n    WP<CColorManagementImageDescription> m_self;\n\n    NColorManagement::PImageDescription  m_settings;\n\n  private:\n    SP<CWpImageDescriptionV1> m_resource;\n    wl_client*                m_client              = nullptr;\n    bool                      m_allowGetInformation = false;\n\n    friend class CColorManagementOutput;\n};\n\nclass CColorManagementImageDescriptionInfo {\n  public:\n    CColorManagementImageDescriptionInfo(SP<CWpImageDescriptionInfoV1> resource, const NColorManagement::SImageDescription& settings_);\n\n    bool       good();\n    wl_client* client();\n\n  private:\n    SP<CWpImageDescriptionInfoV1>       m_resource;\n    wl_client*                          m_client = nullptr;\n    NColorManagement::SImageDescription m_settings;\n};\n\nclass CColorManagementProtocol : public IWaylandProtocol {\n  public:\n    CColorManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name, bool debug = false);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         onImagePreferredChanged(uint32_t preferredId);\n    void         onMonitorImageDescriptionChanged(WP<CMonitor> monitor);\n\n    bool         isClientCMAware(wl_client* client);\n\n  private:\n    void                                               destroyResource(CColorManager* resource);\n    void                                               destroyResource(CColorManagementOutput* resource);\n    void                                               destroyResource(CColorManagementSurface* resource);\n    void                                               destroyResource(CColorManagementFeedbackSurface* resource);\n    void                                               destroyResource(CColorManagementIccCreator* resource);\n    void                                               destroyResource(CColorManagementParametricCreator* resource);\n    void                                               destroyResource(CColorManagementImageDescription* resource);\n\n    std::vector<SP<CColorManager>>                     m_managers;\n    std::vector<SP<CColorManagementOutput>>            m_outputs;\n    std::vector<SP<CColorManagementSurface>>           m_surfaces;\n    std::vector<SP<CColorManagementFeedbackSurface>>   m_feedbackSurfaces;\n    std::vector<SP<CColorManagementIccCreator>>        m_iccCreators;\n    std::vector<SP<CColorManagementParametricCreator>> m_parametricCreators;\n    std::vector<SP<CColorManagementImageDescription>>  m_imageDescriptions;\n    bool                                               m_debug = false;\n\n    friend class CColorManager;\n    friend class CColorManagementOutput;\n    friend class CColorManagementSurface;\n    friend class CColorManagementFeedbackSurface;\n    friend class CColorManagementIccCreator;\n    friend class CColorManagementParametricCreator;\n    friend class CColorManagementImageDescription;\n};\n\nnamespace PROTO {\n    inline UP<CColorManagementProtocol> colorManagement;\n};\n"
  },
  {
    "path": "src/protocols/CommitTiming.cpp",
    "content": "#include \"CommitTiming.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../managers/eventLoop/EventLoopTimer.hpp\"\n\nCCommitTimerResource::CCommitTimerResource(UP<CWpCommitTimerV1>&& resource_, SP<CWLSurfaceResource> surface) : m_resource(std::move(resource_)), m_surface(surface) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setData(this);\n    m_resource->setDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); });\n\n    m_resource->setSetTimestamp([this](CWpCommitTimerV1* r, uint32_t tvHi, uint32_t tvLo, uint32_t tvNsec) {\n        if (!m_surface) {\n            r->error(WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED, \"Surface was gone\");\n            return;\n        }\n\n        if (m_surface->m_pending.pendingTimeout.has_value()) {\n            r->error(WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, \"Timestamp is already set\");\n            return;\n        }\n\n        const auto delay = Time::till({.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo, .tv_nsec = tvNsec});\n\n        if (delay.count() <= 0) {\n            m_surface->m_pending.pendingTimeout.reset();\n        } else\n            m_surface->m_pending.pendingTimeout = delay;\n    });\n\n    m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit2.listen([this](auto state) {\n        if (!state || !state->pendingTimeout.has_value() || !m_surface || m_surface->isTearing())\n            return;\n\n        m_surface->m_stateQueue.lock(state, LOCK_REASON_TIMER);\n\n        if (!state->timer) {\n            state->timer = makeShared<CEventLoopTimer>(\n                state->pendingTimeout,\n                [surface = m_surface, state](SP<CEventLoopTimer> self, void* data) {\n                    if (!surface || !state)\n                        return;\n\n                    surface->m_stateQueue.unlock(state, LOCK_REASON_TIMER);\n                },\n                nullptr);\n            g_pEventLoopManager->addTimer(state->timer);\n        } else\n            state->timer->updateTimeout(state->pendingTimeout);\n\n        state->pendingTimeout.reset();\n    });\n}\n\nbool CCommitTimerResource::good() {\n    return m_resource->resource();\n}\n\nCCommitTimingManagerResource::CCommitTimingManagerResource(UP<CWpCommitTimingManagerV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setData(this);\n    m_resource->setDestroy([this](CWpCommitTimingManagerV1* r) { PROTO::commitTiming->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpCommitTimingManagerV1* r) { PROTO::commitTiming->destroyResource(this); });\n\n    m_resource->setGetTimer([](CWpCommitTimingManagerV1* r, uint32_t id, wl_resource* surfResource) {\n        if (!surfResource) {\n            r->error(-1, \"No resource for commit timing\");\n            return;\n        }\n\n        auto surf = CWLSurfaceResource::fromResource(surfResource);\n\n        if (!surf) {\n            r->error(-1, \"No surface for commit timing\");\n            return;\n        }\n\n        if (surf->m_commitTimer) {\n            r->error(WP_COMMIT_TIMING_MANAGER_V1_ERROR_COMMIT_TIMER_EXISTS, \"Surface already has a commit timing\");\n            return;\n        }\n\n        const auto& RESOURCE = PROTO::commitTiming->m_timers.emplace_back(makeUnique<CCommitTimerResource>(makeUnique<CWpCommitTimerV1>(r->client(), r->version(), id), surf));\n\n        if (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::commitTiming->m_timers.pop_back();\n            return;\n        }\n\n        surf->m_commitTimer = RESOURCE;\n    });\n}\n\nCCommitTimingManagerResource::~CCommitTimingManagerResource() {\n    ;\n}\n\nbool CCommitTimingManagerResource::good() {\n    return m_resource->resource();\n}\n\nCCommitTimingProtocol::CCommitTimingProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CCommitTimingProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CCommitTimingManagerResource>(makeUnique<CWpCommitTimingManagerV1>(client, ver, id))).get();\n\n    if (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CCommitTimingProtocol::destroyResource(CCommitTimingManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n\nvoid CCommitTimingProtocol::destroyResource(CCommitTimerResource* res) {\n    std::erase_if(m_timers, [&](const auto& other) { return other.get() == res; });\n}\n"
  },
  {
    "path": "src/protocols/CommitTiming.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"commit-timing-v1.hpp\"\n\n#include \"../helpers/signal/Signal.hpp\"\n#include \"helpers/time/Time.hpp\"\n\nclass CWLSurfaceResource;\nclass CEventLoopTimer;\n\nclass CCommitTimerResource {\n  public:\n    CCommitTimerResource(UP<CWpCommitTimerV1>&& resource_, SP<CWLSurfaceResource> surface);\n\n    bool good();\n\n  private:\n    UP<CWpCommitTimerV1>   m_resource;\n    WP<CWLSurfaceResource> m_surface;\n\n    struct {\n        CHyprSignalListener surfaceStateCommit;\n    } m_listeners;\n\n    friend class CCommitTimingProtocol;\n    friend class CCommitTimingManagerResource;\n};\n\nclass CCommitTimingManagerResource {\n  public:\n    CCommitTimingManagerResource(UP<CWpCommitTimingManagerV1>&& resource_);\n    ~CCommitTimingManagerResource();\n\n    bool good();\n\n  private:\n    UP<CWpCommitTimingManagerV1> m_resource;\n};\n\nclass CCommitTimingProtocol : public IWaylandProtocol {\n  public:\n    CCommitTimingProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CCommitTimingManagerResource* resource);\n    void destroyResource(CCommitTimerResource* resource);\n\n    //\n    std::vector<UP<CCommitTimingManagerResource>> m_managers;\n    std::vector<UP<CCommitTimerResource>>         m_timers;\n\n    friend class CCommitTimingManagerResource;\n    friend class CCommitTimerResource;\n};\n\nnamespace PROTO {\n    inline UP<CCommitTimingProtocol> commitTiming;\n};\n"
  },
  {
    "path": "src/protocols/ContentType.cpp",
    "content": "#include \"ContentType.hpp\"\n#include \"content-type-v1.hpp\"\n#include \"protocols/types/ContentType.hpp\"\n\nCContentTypeManager::CContentTypeManager(SP<CWpContentTypeManagerV1> resource) : m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); });\n\n    m_resource->setGetSurfaceContentType([](CWpContentTypeManagerV1* r, uint32_t id, wl_resource* surface) {\n        LOGM(Log::TRACE, \"Get surface for id={}, surface={}\", id, (uintptr_t)surface);\n        auto SURF = CWLSurfaceResource::fromResource(surface);\n\n        if (!SURF) {\n            LOGM(Log::ERR, \"No surface for resource {}\", (uintptr_t)surface);\n            r->error(-1, \"Invalid surface (2)\");\n            return;\n        }\n\n        if (SURF->m_contentType) {\n            r->error(WP_CONTENT_TYPE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, \"CT manager already exists\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::contentType->m_contentTypes.emplace_back(makeShared<CContentType>(makeShared<CWpContentTypeV1>(r->client(), r->version(), id)));\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::contentType->m_contentTypes.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        SURF->m_contentType = RESOURCE;\n    });\n}\n\nbool CContentTypeManager::good() {\n    return m_resource->resource();\n}\n\nCContentType::CContentType(WP<CWLSurfaceResource> surface) {\n    m_destroy = surface->m_events.destroy.listen([this] { PROTO::contentType->destroyResource(this); });\n}\n\nCContentType::CContentType(SP<CWpContentTypeV1> resource) : m_resource(resource) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CWpContentTypeV1* r) { PROTO::contentType->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpContentTypeV1* r) { PROTO::contentType->destroyResource(this); });\n\n    m_resource->setSetContentType([this](CWpContentTypeV1* r, wpContentTypeV1Type type) { m_value = NContentType::fromWP(type); });\n}\n\nbool CContentType::good() {\n    return m_resource && m_resource->resource();\n}\n\nwl_client* CContentType::client() {\n    return m_client;\n}\n\nCContentTypeProtocol::CContentTypeProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CContentTypeProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CContentTypeManager>(makeShared<CWpContentTypeManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nSP<CContentType> CContentTypeProtocol::getContentType(WP<CWLSurfaceResource> surface) {\n    if (surface->m_contentType.valid())\n        return surface->m_contentType.lock();\n\n    return m_contentTypes.emplace_back(makeShared<CContentType>(surface));\n}\n\nvoid CContentTypeProtocol::destroyResource(CContentTypeManager* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CContentTypeProtocol::destroyResource(CContentType* resource) {\n    std::erase_if(m_contentTypes, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/ContentType.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"content-type-v1.hpp\"\n#include \"types/ContentType.hpp\"\n\nclass CContentTypeManager {\n  public:\n    CContentTypeManager(SP<CWpContentTypeManagerV1> resource);\n\n    bool good();\n\n  private:\n    SP<CWpContentTypeManagerV1> m_resource;\n};\n\nclass CContentType {\n  public:\n    CContentType(SP<CWpContentTypeV1> resource);\n    CContentType(WP<CWLSurfaceResource> surface);\n\n    bool                       good();\n    wl_client*                 client();\n    NContentType::eContentType m_value = NContentType::CONTENT_TYPE_NONE;\n\n    WP<CContentType>           m_self;\n\n  private:\n    SP<CWpContentTypeV1> m_resource;\n    wl_client*           m_client = nullptr;\n\n    CHyprSignalListener  m_destroy;\n\n    friend class CContentTypeProtocol;\n};\n\nclass CContentTypeProtocol : public IWaylandProtocol {\n  public:\n    CContentTypeProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void     bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    SP<CContentType> getContentType(WP<CWLSurfaceResource> surface);\n\n  private:\n    void                                 destroyResource(CContentTypeManager* resource);\n    void                                 destroyResource(CContentType* resource);\n\n    std::vector<SP<CContentTypeManager>> m_managers;\n    std::vector<SP<CContentType>>        m_contentTypes;\n\n    friend class CContentTypeManager;\n    friend class CContentType;\n};\n\nnamespace PROTO {\n    inline UP<CContentTypeProtocol> contentType;\n};\n"
  },
  {
    "path": "src/protocols/CursorShape.cpp",
    "content": "#include \"CursorShape.hpp\"\n#include <algorithm>\n#include \"../helpers/CursorShapes.hpp\"\n\nCCursorShapeProtocol::CCursorShapeProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CCursorShapeProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [res](const auto& other) { return other->resource() == res; });\n}\n\nvoid CCursorShapeProtocol::onDeviceResourceDestroy(wl_resource* res) {\n    std::erase_if(m_devices, [res](const auto& other) { return other->resource() == res; });\n}\n\nvoid CCursorShapeProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CWpCursorShapeManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CWpCursorShapeManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CWpCursorShapeManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetPointer([this](CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* pointer) { this->onGetPointer(pMgr, id, pointer); });\n    RESOURCE->setGetTabletToolV2([this](CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* tablet) { this->onGetTabletToolV2(pMgr, id, tablet); });\n}\n\nvoid CCursorShapeProtocol::onGetPointer(CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* pointer) {\n    createCursorShapeDevice(pMgr, id, pointer);\n}\n\nvoid CCursorShapeProtocol::onGetTabletToolV2(CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* tablet) {\n    createCursorShapeDevice(pMgr, id, tablet);\n}\n\nvoid CCursorShapeProtocol::createCursorShapeDevice(CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* resource) {\n    const auto  CLIENT   = pMgr->client();\n    const auto& RESOURCE = m_devices.emplace_back(makeUnique<CWpCursorShapeDeviceV1>(CLIENT, pMgr->version(), id));\n    RESOURCE->setOnDestroy([this](CWpCursorShapeDeviceV1* p) { this->onDeviceResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CWpCursorShapeDeviceV1* p) { this->onDeviceResourceDestroy(p->resource()); });\n    RESOURCE->setSetShape([this](CWpCursorShapeDeviceV1* p, uint32_t serial, wpCursorShapeDeviceV1Shape shape) { this->onSetShape(p, serial, shape); });\n}\n\nvoid CCursorShapeProtocol::onSetShape(CWpCursorShapeDeviceV1* pMgr, uint32_t serial, wpCursorShapeDeviceV1Shape shape) {\n    if UNLIKELY (sc<uint32_t>(shape) == 0 || sc<uint32_t>(shape) >= CURSOR_SHAPE_NAMES.size()) {\n        pMgr->error(WP_CURSOR_SHAPE_DEVICE_V1_ERROR_INVALID_SHAPE, \"The shape is invalid\");\n        return;\n    }\n\n    SSetShapeEvent event;\n    event.pMgr      = pMgr;\n    event.shape     = shape;\n    event.shapeName = CURSOR_SHAPE_NAMES.at(shape);\n\n    m_events.setShape.emit(event);\n}\n"
  },
  {
    "path": "src/protocols/CursorShape.hpp",
    "content": "#pragma once\n\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"cursor-shape-v1.hpp\"\n\nclass CCursorShapeProtocol : public IWaylandProtocol {\n  public:\n    CCursorShapeProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct SSetShapeEvent {\n        CWpCursorShapeDeviceV1*    pMgr = nullptr;\n        wpCursorShapeDeviceV1Shape shape;\n        std::string                shapeName;\n    };\n\n    struct {\n        CSignalT<SSetShapeEvent> setShape;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void onDeviceResourceDestroy(wl_resource* res);\n\n    void onGetPointer(CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* pointer);\n    void onGetTabletToolV2(CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* tablet);\n\n    void onSetShape(CWpCursorShapeDeviceV1* pMgr, uint32_t serial, wpCursorShapeDeviceV1Shape shape);\n    void createCursorShapeDevice(CWpCursorShapeManagerV1* pMgr, uint32_t id, wl_resource* resource);\n\n    //\n    std::vector<UP<CWpCursorShapeDeviceV1>>  m_devices;\n    std::vector<UP<CWpCursorShapeManagerV1>> m_managers;\n};\n\nnamespace PROTO {\n    inline UP<CCursorShapeProtocol> cursorShape;\n};\n"
  },
  {
    "path": "src/protocols/DRMLease.cpp",
    "content": "#include \"DRMLease.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"drm-lease-v1.hpp\"\n#include \"managers/eventLoop/EventLoopManager.hpp\"\n#include \"protocols/WaylandProtocol.hpp\"\n#include <algorithm>\n#include <aquamarine/backend/DRM.hpp>\n#include <fcntl.h>\nusing namespace Hyprutils::OS;\n\nCDRMLeaseResource::CDRMLeaseResource(SP<CWpDrmLeaseV1> resource_, SP<CDRMLeaseRequestResource> request) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_parent    = request->m_parent;\n    m_requested = request->m_requested;\n\n    m_resource->setOnDestroy([this](CWpDrmLeaseV1* r) {\n        if (m_parent && PROTO::lease.contains(m_parent->m_deviceName))\n            PROTO::lease.at(m_parent->m_deviceName)->destroyResource(this);\n    });\n    m_resource->setDestroy([this](CWpDrmLeaseV1* r) {\n        if (m_parent && PROTO::lease.contains(m_parent->m_deviceName))\n            PROTO::lease.at(m_parent->m_deviceName)->destroyResource(this);\n    });\n\n    for (auto const& m : m_requested) {\n        if (!m->m_monitor || m->m_monitor->m_isBeingLeased) {\n            LOGM(Log::ERR, \"Rejecting lease: no monitor or monitor is being leased for {}\", (m->m_monitor ? m->m_monitor->m_name : \"null\"));\n            m_resource->sendFinished();\n            return;\n        }\n    }\n\n    // grant the lease if it is seemingly valid\n\n    LOGM(Log::DEBUG, \"Leasing outputs: {}\", [this]() {\n        std::string roll;\n        for (auto const& o : m_requested) {\n            roll += std::format(\"{} \", o->m_monitor->m_name);\n        }\n        return roll;\n    }());\n\n    std::vector<SP<Aquamarine::IOutput>> outputs;\n    // reserve to avoid reallocations\n    outputs.reserve(m_requested.size());\n\n    for (auto const& m : m_requested) {\n        outputs.emplace_back(m->m_monitor->m_output);\n    }\n\n    auto aqlease = Aquamarine::CDRMLease::create(outputs);\n    if (!aqlease) {\n        LOGM(Log::ERR, \"Rejecting lease: backend failed to alloc a lease\");\n        m_resource->sendFinished();\n        return;\n    }\n\n    m_lease = aqlease;\n\n    for (auto const& m : m_requested) {\n        m->m_monitor->m_isBeingLeased = true;\n    }\n\n    m_listeners.destroyLease = m_lease->events.destroy.listen([this] {\n        for (auto const& m : m_requested) {\n            if (m && m->m_monitor)\n                m->m_monitor->m_isBeingLeased = false;\n        }\n\n        m_resource->sendFinished();\n        LOGM(Log::DEBUG, \"Revoking lease for fd {}\", m_lease->leaseFD);\n    });\n\n    LOGM(Log::DEBUG, \"Granting lease, sending fd {}\", m_lease->leaseFD);\n\n    m_resource->sendLeaseFd(m_lease->leaseFD);\n\n    close(m_lease->leaseFD);\n}\n\nbool CDRMLeaseResource::good() {\n    return m_resource->resource();\n}\n\nCDRMLeaseResource::~CDRMLeaseResource() {\n    // destroy in this order to ensure listener gets called\n    m_lease.reset();\n    m_listeners.destroyLease.reset();\n}\n\nCDRMLeaseRequestResource::CDRMLeaseRequestResource(WP<CDRMLeaseDeviceResource> parent_, SP<CWpDrmLeaseRequestV1> resource_) : m_parent(parent_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWpDrmLeaseRequestV1* r) {\n        if (m_parent && PROTO::lease.contains(m_parent->m_deviceName))\n            PROTO::lease.at(m_parent->m_deviceName)->destroyResource(this);\n    });\n\n    m_resource->setRequestConnector([this](CWpDrmLeaseRequestV1* r, wl_resource* conn) {\n        if (!conn) {\n            m_resource->error(-1, \"Null connector\");\n            return;\n        }\n\n        auto CONNECTOR = CDRMLeaseConnectorResource::fromResource(conn);\n\n        if (std::ranges::find(m_requested, CONNECTOR) != m_requested.end()) {\n            m_resource->error(WP_DRM_LEASE_REQUEST_V1_ERROR_DUPLICATE_CONNECTOR, \"Connector already requested\");\n            return;\n        }\n\n        auto& lease = PROTO::lease.at(m_parent->m_deviceName);\n\n        if (std::ranges::find(lease->m_connectors.begin(), lease->m_connectors.end(), CONNECTOR) == lease->m_connectors.end()) {\n            m_resource->error(WP_DRM_LEASE_REQUEST_V1_ERROR_WRONG_DEVICE, \"Connector requested for wrong device\");\n            return;\n        }\n\n        m_requested.emplace_back(CONNECTOR);\n    });\n\n    m_resource->setSubmit([this](CWpDrmLeaseRequestV1* r, uint32_t id) {\n        if (m_requested.empty()) {\n            m_resource->error(WP_DRM_LEASE_REQUEST_V1_ERROR_EMPTY_LEASE, \"No connectors added\");\n            return;\n        }\n\n        auto RESOURCE = makeShared<CDRMLeaseResource>(makeShared<CWpDrmLeaseV1>(m_resource->client(), m_resource->version(), id), m_self.lock());\n        if UNLIKELY (!RESOURCE) {\n            m_resource->noMemory();\n            return;\n        }\n\n        PROTO::lease.at(m_parent->m_deviceName)->m_leases.emplace_back(RESOURCE);\n\n        // per protocol, after submit, this is dead.\n        PROTO::lease.at(m_parent->m_deviceName)->destroyResource(this);\n    });\n}\n\nbool CDRMLeaseRequestResource::good() {\n    return m_resource->resource();\n}\n\nSP<CDRMLeaseConnectorResource> CDRMLeaseConnectorResource::fromResource(wl_resource* res) {\n    auto data = sc<CDRMLeaseConnectorResource*>(sc<CWpDrmLeaseConnectorV1*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nCDRMLeaseConnectorResource::CDRMLeaseConnectorResource(WP<CDRMLeaseDeviceResource> parent_, SP<CWpDrmLeaseConnectorV1> resource_, PHLMONITOR monitor_) :\n    m_parent(parent_), m_monitor(monitor_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWpDrmLeaseConnectorV1* r) {\n        if (m_parent && PROTO::lease.contains(m_parent->m_deviceName))\n            PROTO::lease.at(m_parent->m_deviceName)->destroyResource(this);\n    });\n    m_resource->setDestroy([this](CWpDrmLeaseConnectorV1* r) {\n        if (m_parent && PROTO::lease.contains(m_parent->m_deviceName))\n            PROTO::lease.at(m_parent->m_deviceName)->destroyResource(this);\n    });\n\n    m_resource->setData(this);\n\n    m_listeners.destroyMonitor = m_monitor->m_events.destroy.listen([this] {\n        m_resource->sendWithdrawn();\n        m_dead = true;\n    });\n}\n\nbool CDRMLeaseConnectorResource::good() {\n    return m_resource->resource();\n}\n\nvoid CDRMLeaseConnectorResource::sendData() {\n    m_resource->sendName(m_monitor->m_name.c_str());\n    m_resource->sendDescription(m_monitor->m_description.c_str());\n\n    auto AQDRMOutput = sc<Aquamarine::CDRMOutput*>(m_monitor->m_output.get());\n    m_resource->sendConnectorId(AQDRMOutput->getConnectorID());\n\n    m_resource->sendDone();\n}\n\nCDRMLeaseDeviceResource::CDRMLeaseDeviceResource(std::string deviceName_, SP<CWpDrmLeaseDeviceV1> resource_) : m_deviceName(deviceName_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWpDrmLeaseDeviceV1* r) {\n        if (PROTO::lease.contains(m_deviceName))\n            PROTO::lease.at(m_deviceName)->destroyResource(this);\n    });\n    m_resource->setRelease([this](CWpDrmLeaseDeviceV1* r) {\n        if (PROTO::lease.contains(m_deviceName))\n            PROTO::lease.at(m_deviceName)->destroyResource(this);\n    });\n\n    m_resource->setCreateLeaseRequest([this](CWpDrmLeaseDeviceV1* r, uint32_t id) {\n        auto RESOURCE = makeShared<CDRMLeaseRequestResource>(m_self, makeShared<CWpDrmLeaseRequestV1>(m_resource->client(), m_resource->version(), id));\n        if UNLIKELY (!RESOURCE) {\n            m_resource->noMemory();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        PROTO::lease.at(m_deviceName)->m_requests.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New lease request {}\", id);\n\n        RESOURCE->m_parent = m_self;\n    });\n\n    CFileDescriptor fd{PROTO::lease.at(m_deviceName)->m_backend.get()->getNonMasterFD()};\n    if (!fd.isValid()) {\n        LOGM(Log::ERR, \"Failed to dup fd in lease\");\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"Sending DRMFD {} to new lease device\", fd.get());\n    m_resource->sendDrmFd(fd.get());\n\n    for (auto const& m : PROTO::lease.at(m_deviceName)->m_offeredOutputs) {\n        if (m)\n            sendConnector(m.lock());\n    }\n\n    m_resource->sendDone();\n}\n\nbool CDRMLeaseDeviceResource::good() {\n    return m_resource->resource();\n}\n\nvoid CDRMLeaseDeviceResource::sendConnector(PHLMONITOR monitor) {\n    if (std::ranges::find_if(m_connectorsSent, [monitor](const auto& e) { return e && !e->m_dead && e->m_monitor == monitor; }) != m_connectorsSent.end())\n        return;\n\n    auto RESOURCE = makeShared<CDRMLeaseConnectorResource>(m_self, makeShared<CWpDrmLeaseConnectorV1>(m_resource->client(), m_resource->version(), 0), monitor);\n    if UNLIKELY (!RESOURCE) {\n        m_resource->noMemory();\n        return;\n    }\n\n    RESOURCE->m_parent = m_self;\n    RESOURCE->m_self   = RESOURCE;\n\n    LOGM(Log::DEBUG, \"Sending new connector {}\", monitor->m_name);\n\n    m_connectorsSent.emplace_back(RESOURCE);\n    PROTO::lease.at(m_deviceName)->m_connectors.emplace_back(RESOURCE);\n\n    m_resource->sendConnector(RESOURCE->m_resource.get());\n\n    RESOURCE->sendData();\n}\n\nCDRMLeaseProtocol::CDRMLeaseProtocol(const wl_interface* iface, const int& ver, const std::string& name, SP<Aquamarine::IBackendImplementation> backend_) :\n    IWaylandProtocol(iface, ver, name) {\n    if (backend_->type() != Aquamarine::AQ_BACKEND_DRM)\n        return;\n\n    m_backend    = sc<Aquamarine::CDRMBackend*>(backend_.get())->self.lock();\n    m_deviceName = m_backend->gpuName;\n\n    CFileDescriptor fd{m_backend->getNonMasterFD()};\n\n    if (!fd.isValid()) {\n        LOGM(Log::ERR, \"Failed to dup fd for drm node {}\", m_deviceName);\n        return;\n    }\n\n    m_success = true;\n}\n\nvoid CDRMLeaseProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CDRMLeaseDeviceResource>(m_deviceName, makeShared<CWpDrmLeaseDeviceV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self = RESOURCE;\n}\n\nvoid CDRMLeaseProtocol::destroyResource(CDRMLeaseDeviceResource* resource) {\n    std::erase_if(m_managers, [resource](const auto& e) { return e.get() == resource; });\n}\n\nvoid CDRMLeaseProtocol::destroyResource(CDRMLeaseConnectorResource* resource) {\n    for (const auto& m : m_managers) {\n        std::erase_if(m->m_connectorsSent, [resource](const auto& e) { return e.expired() || e->m_dead || e.get() == resource; });\n    }\n    std::erase_if(m_connectors, [resource](const auto& e) { return e.get() == resource; });\n}\n\nvoid CDRMLeaseProtocol::destroyResource(CDRMLeaseRequestResource* resource) {\n    std::erase_if(m_requests, [resource](const auto& e) { return e.get() == resource; });\n}\n\nvoid CDRMLeaseProtocol::destroyResource(CDRMLeaseResource* resource) {\n    std::erase_if(m_leases, [resource](const auto& e) { return e.get() == resource; });\n}\n\nvoid CDRMLeaseProtocol::offer(PHLMONITOR monitor) {\n    std::erase_if(m_offeredOutputs, [](const auto& e) { return e.expired(); });\n    if (std::ranges::find(m_offeredOutputs.begin(), m_offeredOutputs.end(), monitor) != m_offeredOutputs.end())\n        return;\n\n    if (monitor->m_output->getBackend()->type() != Aquamarine::AQ_BACKEND_DRM)\n        return;\n\n    if (monitor->m_output->getBackend() != m_backend) {\n        LOGM(Log::ERR, \"Monitor {} cannot be leased: lease is for a different device\", monitor->m_name);\n        return;\n    }\n\n    m_offeredOutputs.emplace_back(monitor);\n\n    for (auto const& m : m_managers) {\n        m->sendConnector(monitor);\n        m->m_resource->sendDone();\n    }\n}\n\nstd::string CDRMLeaseProtocol::getDeviceName() {\n    return m_deviceName;\n}\n\nSP<Aquamarine::IBackendImplementation> CDRMLeaseProtocol::getBackend() {\n    return m_backend;\n}\n\nbool CDRMLeaseProtocol::good() {\n    return m_success;\n}\n"
  },
  {
    "path": "src/protocols/DRMLease.hpp",
    "content": "#pragma once\n\n#include <aquamarine/backend/Backend.hpp>\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"drm-lease-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\n/*\n    TODO: this protocol is not made for systems with multiple DRM nodes (e.g. multigpu)\n*/\n\nAQUAMARINE_FORWARD(CDRMBackend);\nAQUAMARINE_FORWARD(CDRMLease);\nclass CDRMLeaseDeviceResource;\nclass CMonitor;\nclass CDRMLeaseProtocol;\nclass CDRMLeaseConnectorResource;\nclass CDRMLeaseRequestResource;\n\nclass CDRMLeaseResource {\n  public:\n    CDRMLeaseResource(SP<CWpDrmLeaseV1> resource_, SP<CDRMLeaseRequestResource> request);\n    ~CDRMLeaseResource();\n\n    bool                                        good();\n\n    WP<CDRMLeaseDeviceResource>                 m_parent;\n    std::vector<WP<CDRMLeaseConnectorResource>> m_requested;\n    SP<Aquamarine::CDRMLease>                   m_lease;\n\n    struct {\n        CHyprSignalListener destroyLease;\n    } m_listeners;\n\n  private:\n    SP<CWpDrmLeaseV1> m_resource;\n};\n\nclass CDRMLeaseRequestResource {\n  public:\n    CDRMLeaseRequestResource(WP<CDRMLeaseDeviceResource> parent_, SP<CWpDrmLeaseRequestV1> resource_);\n\n    bool                                        good();\n\n    WP<CDRMLeaseDeviceResource>                 m_parent;\n    WP<CDRMLeaseRequestResource>                m_self;\n    std::vector<WP<CDRMLeaseConnectorResource>> m_requested;\n\n  private:\n    SP<CWpDrmLeaseRequestV1> m_resource;\n};\n\nclass CDRMLeaseConnectorResource {\n  public:\n    CDRMLeaseConnectorResource(WP<CDRMLeaseDeviceResource> parent_, SP<CWpDrmLeaseConnectorV1> resource_, PHLMONITOR monitor_);\n    static SP<CDRMLeaseConnectorResource> fromResource(wl_resource*);\n\n    bool                                  good();\n    void                                  sendData();\n\n    WP<CDRMLeaseConnectorResource>        m_self;\n    WP<CDRMLeaseDeviceResource>           m_parent;\n    PHLMONITORREF                         m_monitor;\n    bool                                  m_dead = false;\n\n  private:\n    SP<CWpDrmLeaseConnectorV1> m_resource;\n\n    struct {\n        CHyprSignalListener destroyMonitor;\n    } m_listeners;\n\n    friend class CDRMLeaseDeviceResource;\n};\n\nclass CDRMLeaseDeviceResource {\n  public:\n    CDRMLeaseDeviceResource(std::string deviceName, SP<CWpDrmLeaseDeviceV1> resource_);\n\n    bool                                        good();\n    void                                        sendConnector(PHLMONITOR monitor);\n\n    std::vector<WP<CDRMLeaseConnectorResource>> m_connectorsSent;\n\n    WP<CDRMLeaseDeviceResource>                 m_self;\n    std::string                                 m_deviceName;\n\n  private:\n    SP<CWpDrmLeaseDeviceV1> m_resource;\n\n    friend class CDRMLeaseProtocol;\n};\n\nclass CDRMLeaseProtocol : public IWaylandProtocol {\n  public:\n    CDRMLeaseProtocol(const wl_interface* iface, const int& ver, const std::string& name, SP<Aquamarine::IBackendImplementation> backend);\n\n    virtual void                           bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void                                   offer(PHLMONITOR monitor);\n\n    SP<Aquamarine::IBackendImplementation> getBackend();\n    std::string                            getDeviceName();\n    bool                                   good();\n\n  private:\n    void destroyResource(CDRMLeaseDeviceResource* resource);\n    void destroyResource(CDRMLeaseConnectorResource* resource);\n    void destroyResource(CDRMLeaseRequestResource* resource);\n    void destroyResource(CDRMLeaseResource* resource);\n\n    //\n    std::vector<SP<CDRMLeaseDeviceResource>>    m_managers;\n    std::vector<SP<CDRMLeaseConnectorResource>> m_connectors;\n    std::vector<SP<CDRMLeaseRequestResource>>   m_requests;\n    std::vector<SP<CDRMLeaseResource>>          m_leases;\n\n    std::string                                 m_deviceName = \"\";\n    bool                                        m_success    = false;\n    SP<Aquamarine::CDRMBackend>                 m_backend;\n    std::vector<PHLMONITORREF>                  m_offeredOutputs;\n\n    friend class CDRMLeaseDeviceResource;\n    friend class CDRMLeaseConnectorResource;\n    friend class CDRMLeaseRequestResource;\n    friend class CDRMLeaseResource;\n};\n\nnamespace PROTO {\n    inline std::unordered_map<std::string, SP<CDRMLeaseProtocol>> lease;\n};\n"
  },
  {
    "path": "src/protocols/DRMSyncobj.cpp",
    "content": "#include \"DRMSyncobj.hpp\"\n#include <algorithm>\n\n#include \"core/Compositor.hpp\"\n#include \"../helpers/sync/SyncTimeline.hpp\"\n#include \"../Compositor.hpp\"\n#include \"render/OpenGL.hpp\"\n\n#include <fcntl.h>\nusing namespace Hyprutils::OS;\n\nCDRMSyncPointState::CDRMSyncPointState(SP<CSyncTimeline> timeline_, uint64_t point_) : m_timeline(timeline_), m_point(point_) {}\n\nconst uint64_t& CDRMSyncPointState::point() {\n    return m_point;\n}\n\nWP<CSyncTimeline> CDRMSyncPointState::timeline() {\n    return m_timeline;\n}\n\nUP<CSyncReleaser> CDRMSyncPointState::createSyncRelease() {\n    if (m_releaseTaken)\n        Log::logger->log(Log::ERR, \"CDRMSyncPointState: creating a sync releaser on an already created SyncRelease\");\n\n    m_releaseTaken = true;\n    return makeUnique<CSyncReleaser>(m_timeline, m_point);\n}\n\nbool CDRMSyncPointState::addWaiter(std::function<void()>&& waiter) {\n    m_acquireCommitted = true;\n    return m_timeline->addWaiter(std::move(waiter), m_point, 0u);\n}\n\nbool CDRMSyncPointState::committed() {\n    return m_acquireCommitted;\n}\n\nCFileDescriptor CDRMSyncPointState::exportAsFD() {\n    return m_timeline->exportAsSyncFileFD(m_point);\n}\n\nvoid CDRMSyncPointState::signal() {\n    m_timeline->signal(m_point);\n}\n\nCDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_) :\n    m_surface(surface_), m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setOnDestroy([this](CWpLinuxDrmSyncobjSurfaceV1* r) { PROTO::sync->destroyResource(this); });\n    m_resource->setDestroy([this](CWpLinuxDrmSyncobjSurfaceV1* r) { PROTO::sync->destroyResource(this); });\n\n    m_resource->setSetAcquirePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) {\n        if (!m_surface) {\n            m_resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, \"Surface is gone\");\n            return;\n        }\n\n        auto timeline    = CDRMSyncobjTimelineResource::fromResource(timeline_);\n        m_pendingAcquire = {timeline->m_timeline, (sc<uint64_t>(hi) << 32) | sc<uint64_t>(lo)};\n    });\n\n    m_resource->setSetReleasePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) {\n        if (!m_surface) {\n            m_resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, \"Surface is gone\");\n            return;\n        }\n\n        auto timeline    = CDRMSyncobjTimelineResource::fromResource(timeline_);\n        m_pendingRelease = {timeline->m_timeline, (sc<uint64_t>(hi) << 32) | sc<uint64_t>(lo)};\n    });\n\n    m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) {\n        if (!state->updated.bits.buffer || !state->buffer) {\n            if (m_pendingAcquire.timeline() || m_pendingRelease.timeline()) {\n                m_resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, \"Missing buffer\");\n                state->rejected = true;\n            }\n            return;\n        }\n\n        if (!m_pendingAcquire.timeline()) {\n            m_resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT, \"Missing acquire timeline\");\n            state->rejected = true;\n            return;\n        }\n\n        if (!m_pendingRelease.timeline()) {\n            m_resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT, \"Missing release timeline\");\n            state->rejected = true;\n            return;\n        }\n\n        if (m_pendingAcquire.timeline() == m_pendingRelease.timeline() && m_pendingAcquire.point() >= m_pendingRelease.point()) {\n            m_resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, \"Acquire and release points are on the same timeline, and acquire >= release\");\n            state->rejected = true;\n            return;\n        }\n\n        state->updated.bits.acquire = true;\n        state->acquire              = m_pendingAcquire;\n        m_surface->m_stateQueue.lock(state, LOCK_REASON_FENCE);\n        m_pendingAcquire = {};\n\n        state->buffer->addReleasePoint(m_pendingRelease);\n        m_pendingRelease = {};\n    });\n}\n\nbool CDRMSyncobjSurfaceResource::good() {\n    return m_resource->resource();\n}\n\nCDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(UP<CWpLinuxDrmSyncobjTimelineV1>&& resource_, CFileDescriptor&& fd_) :\n    m_fd(std::move(fd_)), m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setOnDestroy([this](CWpLinuxDrmSyncobjTimelineV1* r) { PROTO::sync->destroyResource(this); });\n    m_resource->setDestroy([this](CWpLinuxDrmSyncobjTimelineV1* r) { PROTO::sync->destroyResource(this); });\n\n    m_timeline = CSyncTimeline::create(PROTO::sync->m_drmFD, std::move(m_fd));\n\n    if (!m_timeline) {\n        m_resource->error(WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_INVALID_TIMELINE, \"Timeline failed importing\");\n        return;\n    }\n}\n\nWP<CDRMSyncobjTimelineResource> CDRMSyncobjTimelineResource::fromResource(wl_resource* res) {\n    for (const auto& r : PROTO::sync->m_timelines) {\n        if (r && r->m_resource && r->m_resource->resource() == res)\n            return r;\n    }\n\n    return {};\n}\n\nbool CDRMSyncobjTimelineResource::good() {\n    return m_resource->resource();\n}\n\nCDRMSyncobjManagerResource::CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjManagerV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWpLinuxDrmSyncobjManagerV1* r) { PROTO::sync->destroyResource(this); });\n    m_resource->setDestroy([this](CWpLinuxDrmSyncobjManagerV1* r) { PROTO::sync->destroyResource(this); });\n\n    m_resource->setGetSurface([this](CWpLinuxDrmSyncobjManagerV1* r, uint32_t id, wl_resource* surf) {\n        if UNLIKELY (!surf) {\n            m_resource->error(-1, \"Invalid surface\");\n            return;\n        }\n\n        auto SURF = CWLSurfaceResource::fromResource(surf);\n        if UNLIKELY (!SURF) {\n            m_resource->error(-1, \"Invalid surface (2)\");\n            return;\n        }\n\n        if UNLIKELY (SURF->m_syncobj) {\n            m_resource->error(WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, \"Surface already has a syncobj attached\");\n            return;\n        }\n\n        const auto& RESOURCE = PROTO::sync->m_surfaces.emplace_back(\n            makeUnique<CDRMSyncobjSurfaceResource>(makeUnique<CWpLinuxDrmSyncobjSurfaceV1>(m_resource->client(), m_resource->version(), id), SURF));\n        if UNLIKELY (!RESOURCE->good()) {\n            m_resource->noMemory();\n            PROTO::sync->m_surfaces.pop_back();\n            return;\n        }\n\n        SURF->m_syncobj = RESOURCE;\n\n        LOGM(Log::DEBUG, \"New linux_syncobj at {:x} for surface {:x}\", (uintptr_t)RESOURCE.get(), (uintptr_t)SURF.get());\n    });\n\n    m_resource->setImportTimeline([this](CWpLinuxDrmSyncobjManagerV1* r, uint32_t id, int32_t fd) {\n        const auto& RESOURCE = PROTO::sync->m_timelines.emplace_back(\n            makeUnique<CDRMSyncobjTimelineResource>(makeUnique<CWpLinuxDrmSyncobjTimelineV1>(m_resource->client(), m_resource->version(), id), CFileDescriptor{fd}));\n        if UNLIKELY (!RESOURCE->good()) {\n            m_resource->noMemory();\n            PROTO::sync->m_timelines.pop_back();\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"New linux_drm_timeline at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n}\n\nbool CDRMSyncobjManagerResource::good() {\n    return m_resource->resource();\n}\n\nCDRMSyncobjProtocol::CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    if (g_pCompositor->m_drmRenderNode.syncObjSupport)\n        m_drmFD = g_pCompositor->m_drmRenderNode.fd;\n    else if (g_pCompositor->m_drm.syncobjSupport)\n        m_drmFD = g_pCompositor->m_drm.fd;\n    else {\n        LOGM(Log::ERR, \"CDRMSyncobjProtocol: no nodes support explicit sync?\");\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"CDRMSyncobjProtocol: using fd {}\", m_drmFD);\n}\n\nvoid CDRMSyncobjProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto& RESOURCE = m_managers.emplace_back(makeUnique<CDRMSyncobjManagerResource>(makeUnique<CWpLinuxDrmSyncobjManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CDRMSyncobjProtocol::destroyResource(CDRMSyncobjManagerResource* resource) {\n    std::erase_if(m_managers, [resource](const auto& e) { return e.get() == resource; });\n}\n\nvoid CDRMSyncobjProtocol::destroyResource(CDRMSyncobjTimelineResource* resource) {\n    std::erase_if(m_timelines, [resource](const auto& e) { return e.get() == resource; });\n}\n\nvoid CDRMSyncobjProtocol::destroyResource(CDRMSyncobjSurfaceResource* resource) {\n    std::erase_if(m_surfaces, [resource](const auto& e) { return e.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/DRMSyncobj.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"../helpers/sync/SyncReleaser.hpp\"\n#include \"linux-drm-syncobj-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CWLSurfaceResource;\nclass CDRMSyncobjTimelineResource;\nclass CSyncTimeline;\n\nclass CDRMSyncPointState {\n  public:\n    CDRMSyncPointState() = default;\n    CDRMSyncPointState(SP<CSyncTimeline> timeline_, uint64_t point_);\n    ~CDRMSyncPointState() = default;\n\n    const uint64_t&                                  point();\n    WP<CSyncTimeline>                                timeline();\n    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();\n    bool                                             addWaiter(std::function<void()>&& waiter);\n    bool                                             committed();\n    Hyprutils::OS::CFileDescriptor                   exportAsFD();\n    void                                             signal();\n\n    //\n    operator bool() const {\n        return m_timeline;\n    }\n\n  private:\n    SP<CSyncTimeline> m_timeline         = {};\n    uint64_t          m_point            = 0;\n    bool              m_acquireCommitted = false;\n    bool              m_releaseTaken     = false;\n};\n\nclass CDRMSyncobjSurfaceResource {\n  public:\n    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);\n\n    bool good();\n\n  private:\n    WP<CWLSurfaceResource>          m_surface;\n    UP<CWpLinuxDrmSyncobjSurfaceV1> m_resource;\n\n    CDRMSyncPointState              m_pendingAcquire;\n    CDRMSyncPointState              m_pendingRelease;\n\n    struct {\n        CHyprSignalListener surfaceStateCommit;\n    } m_listeners;\n};\n\nclass CDRMSyncobjTimelineResource {\n  public:\n    CDRMSyncobjTimelineResource(UP<CWpLinuxDrmSyncobjTimelineV1>&& resource_, Hyprutils::OS::CFileDescriptor&& fd_);\n    ~CDRMSyncobjTimelineResource() = default;\n    static WP<CDRMSyncobjTimelineResource> fromResource(wl_resource*);\n\n    bool                                   good();\n\n    Hyprutils::OS::CFileDescriptor         m_fd;\n    SP<CSyncTimeline>                      m_timeline;\n\n  private:\n    UP<CWpLinuxDrmSyncobjTimelineV1> m_resource;\n};\n\nclass CDRMSyncobjManagerResource {\n  public:\n    CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjManagerV1>&& resource_);\n    ~CDRMSyncobjManagerResource() = default;\n\n    bool good();\n\n  private:\n    UP<CWpLinuxDrmSyncobjManagerV1> m_resource;\n};\n\nclass CDRMSyncobjProtocol : public IWaylandProtocol {\n  public:\n    CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n    ~CDRMSyncobjProtocol() = default;\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CDRMSyncobjManagerResource* resource);\n    void destroyResource(CDRMSyncobjTimelineResource* resource);\n    void destroyResource(CDRMSyncobjSurfaceResource* resource);\n\n    //\n    std::vector<UP<CDRMSyncobjManagerResource>>  m_managers;\n    std::vector<UP<CDRMSyncobjTimelineResource>> m_timelines;\n    std::vector<UP<CDRMSyncobjSurfaceResource>>  m_surfaces;\n\n    //\n    int m_drmFD = -1;\n\n    friend class CDRMSyncobjManagerResource;\n    friend class CDRMSyncobjTimelineResource;\n    friend class CDRMSyncobjSurfaceResource;\n};\n\nnamespace PROTO {\n    inline UP<CDRMSyncobjProtocol> sync;\n};\n"
  },
  {
    "path": "src/protocols/DataDeviceWlr.cpp",
    "content": "#include \"DataDeviceWlr.hpp\"\n#include <algorithm>\n#include \"../managers/SeatManager.hpp\"\n#include \"core/Seat.hpp\"\nusing namespace Hyprutils::OS;\n\nCWLRDataOffer::CWLRDataOffer(SP<CZwlrDataControlOfferV1> resource_, SP<IDataSource> source_) : m_source(source_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwlrDataControlOfferV1* r) { PROTO::dataWlr->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrDataControlOfferV1* r) { PROTO::dataWlr->destroyResource(this); });\n\n    m_resource->setReceive([this](CZwlrDataControlOfferV1* r, const char* mime, int32_t fd) {\n        CFileDescriptor sendFd{fd};\n        if (!m_source) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer w/o a source\");\n            return;\n        }\n\n        if (m_dead) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer that's dead\");\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"Offer {:x} asks to send data from source {:x}\", (uintptr_t)this, (uintptr_t)m_source.get());\n\n        m_source->send(mime, std::move(sendFd));\n    });\n}\n\nbool CWLRDataOffer::good() {\n    return m_resource->resource();\n}\n\nvoid CWLRDataOffer::sendData() {\n    if UNLIKELY (!m_source)\n        return;\n\n    for (auto const& m : m_source->mimes()) {\n        m_resource->sendOffer(m.c_str());\n    }\n}\n\nCWLRDataSource::CWLRDataSource(SP<CZwlrDataControlSourceV1> resource_, SP<CWLRDataDevice> device_) : m_device(device_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CZwlrDataControlSourceV1* r) {\n        m_events.destroy.emit();\n        PROTO::dataWlr->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CZwlrDataControlSourceV1* r) {\n        m_events.destroy.emit();\n        PROTO::dataWlr->destroyResource(this);\n    });\n\n    m_resource->setOffer([this](CZwlrDataControlSourceV1* r, const char* mime) { m_mimeTypes.emplace_back(mime); });\n}\n\nCWLRDataSource::~CWLRDataSource() {\n    m_events.destroy.emit();\n}\n\nSP<CWLRDataSource> CWLRDataSource::fromResource(wl_resource* res) {\n    auto data = sc<CWLRDataSource*>(sc<CZwlrDataControlSourceV1*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CWLRDataSource::good() {\n    return m_resource->resource();\n}\n\nstd::vector<std::string> CWLRDataSource::mimes() {\n    return m_mimeTypes;\n}\n\nvoid CWLRDataSource::send(const std::string& mime, CFileDescriptor fd) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) {\n        LOGM(Log::ERR, \"Compositor/App bug: CWLRDataSource::sendAskSend with non-existent mime\");\n        return;\n    }\n\n    m_resource->sendSend(mime.c_str(), fd.get());\n}\n\nvoid CWLRDataSource::accepted(const std::string& mime) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end())\n        LOGM(Log::ERR, \"Compositor/App bug: CWLRDataSource::sendAccepted with non-existent mime\");\n\n    // wlr has no accepted\n}\n\nvoid CWLRDataSource::cancelled() {\n    m_resource->sendCancelled();\n}\n\nvoid CWLRDataSource::error(uint32_t code, const std::string& msg) {\n    m_resource->error(code, msg);\n}\n\nCWLRDataDevice::CWLRDataDevice(SP<CZwlrDataControlDeviceV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CZwlrDataControlDeviceV1* r) { PROTO::dataWlr->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrDataControlDeviceV1* r) { PROTO::dataWlr->destroyResource(this); });\n\n    m_resource->setSetSelection([](CZwlrDataControlDeviceV1* r, wl_resource* sourceR) {\n        auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer<CWLRDataSource>{};\n        if (!source) {\n            LOGM(Log::DEBUG, \"wlr reset selection received\");\n            g_pSeatManager->setCurrentSelection(nullptr);\n            return;\n        }\n\n        if (source && source->used())\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        LOGM(Log::DEBUG, \"wlr manager requests selection to {:x}\", (uintptr_t)source.get());\n        g_pSeatManager->setCurrentSelection(source);\n    });\n\n    m_resource->setSetPrimarySelection([](CZwlrDataControlDeviceV1* r, wl_resource* sourceR) {\n        auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer<CWLRDataSource>{};\n        if (!source) {\n            LOGM(Log::DEBUG, \"wlr reset primary selection received\");\n            g_pSeatManager->setCurrentPrimarySelection(nullptr);\n            return;\n        }\n\n        if (source && source->used())\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        LOGM(Log::DEBUG, \"wlr manager requests primary selection to {:x}\", (uintptr_t)source.get());\n        g_pSeatManager->setCurrentPrimarySelection(source);\n    });\n}\n\nbool CWLRDataDevice::good() {\n    return m_resource->resource();\n}\n\nwl_client* CWLRDataDevice::client() {\n    return m_client;\n}\n\nvoid CWLRDataDevice::sendInitialSelections() {\n    PROTO::dataWlr->sendSelectionToDevice(self.lock(), g_pSeatManager->m_selection.currentSelection.lock(), false);\n    PROTO::dataWlr->sendSelectionToDevice(self.lock(), g_pSeatManager->m_selection.currentPrimarySelection.lock(), true);\n}\n\nvoid CWLRDataDevice::sendDataOffer(SP<CWLRDataOffer> offer) {\n    m_resource->sendDataOffer(offer->m_resource.get());\n}\n\nvoid CWLRDataDevice::sendSelection(SP<CWLRDataOffer> selection) {\n    m_resource->sendSelection(selection->m_resource.get());\n}\n\nvoid CWLRDataDevice::sendPrimarySelection(SP<CWLRDataOffer> selection) {\n    m_resource->sendPrimarySelection(selection->m_resource.get());\n}\n\nCWLRDataControlManagerResource::CWLRDataControlManagerResource(SP<CZwlrDataControlManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwlrDataControlManagerV1* r) { PROTO::dataWlr->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrDataControlManagerV1* r) { PROTO::dataWlr->destroyResource(this); });\n\n    m_resource->setGetDataDevice([this](CZwlrDataControlManagerV1* r, uint32_t id, wl_resource* seat) {\n        const auto RESOURCE = PROTO::dataWlr->m_devices.emplace_back(makeShared<CWLRDataDevice>(makeShared<CZwlrDataControlDeviceV1>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::dataWlr->m_devices.pop_back();\n            return;\n        }\n\n        RESOURCE->self = RESOURCE;\n        m_device       = RESOURCE;\n\n        for (auto const& s : m_sources) {\n            if (!s)\n                continue;\n            s->m_device = RESOURCE;\n        }\n\n        RESOURCE->sendInitialSelections();\n\n        LOGM(Log::DEBUG, \"New wlr data device bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n\n    m_resource->setCreateDataSource([this](CZwlrDataControlManagerV1* r, uint32_t id) {\n        std::erase_if(m_sources, [](const auto& e) { return e.expired(); });\n\n        const auto RESOURCE =\n            PROTO::dataWlr->m_sources.emplace_back(makeShared<CWLRDataSource>(makeShared<CZwlrDataControlSourceV1>(r->client(), r->version(), id), m_device.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::dataWlr->m_sources.pop_back();\n            return;\n        }\n\n        if (!m_device)\n            LOGM(Log::WARN, \"New data source before a device was created\");\n\n        RESOURCE->m_self = RESOURCE;\n\n        m_sources.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New wlr data source bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n}\n\nbool CWLRDataControlManagerResource::good() {\n    return m_resource->resource();\n}\n\nCDataDeviceWLRProtocol::CDataDeviceWLRProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CDataDeviceWLRProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CWLRDataControlManagerResource>(makeShared<CZwlrDataControlManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New wlr_data_control_manager at {:x}\", (uintptr_t)RESOURCE.get());\n}\n\nvoid CDataDeviceWLRProtocol::destroyResource(CWLRDataControlManagerResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CDataDeviceWLRProtocol::destroyResource(CWLRDataSource* resource) {\n    std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CDataDeviceWLRProtocol::destroyResource(CWLRDataDevice* resource) {\n    std::erase_if(m_devices, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CDataDeviceWLRProtocol::destroyResource(CWLRDataOffer* resource) {\n    std::erase_if(m_offers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CDataDeviceWLRProtocol::sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<IDataSource> sel, bool primary) {\n    if (!sel) {\n        if (primary)\n            dev->m_resource->sendPrimarySelectionRaw(nullptr);\n        else\n            dev->m_resource->sendSelectionRaw(nullptr);\n        return;\n    }\n\n    const auto OFFER = m_offers.emplace_back(makeShared<CWLRDataOffer>(makeShared<CZwlrDataControlOfferV1>(dev->m_resource->client(), dev->m_resource->version(), 0), sel));\n\n    if (!OFFER->good()) {\n        dev->m_resource->noMemory();\n        m_offers.pop_back();\n        return;\n    }\n\n    OFFER->m_primary = primary;\n\n    LOGM(Log::DEBUG, \"New {}offer {:x} for data source {:x}\", primary ? \"primary \" : \" \", (uintptr_t)OFFER.get(), (uintptr_t)sel.get());\n\n    dev->sendDataOffer(OFFER);\n    OFFER->sendData();\n    if (primary)\n        dev->sendPrimarySelection(OFFER);\n    else\n        dev->sendSelection(OFFER);\n}\n\nvoid CDataDeviceWLRProtocol::setSelection(SP<IDataSource> source, bool primary) {\n    for (auto const& o : m_offers) {\n        if (o->m_source && o->m_source->hasDnd())\n            continue;\n        if (o->m_primary != primary)\n            continue;\n        o->m_dead = true;\n    }\n\n    if (!source) {\n        LOGM(Log::DEBUG, \"resetting {}selection\", primary ? \"primary \" : \" \");\n\n        for (auto const& d : m_devices) {\n            sendSelectionToDevice(d, nullptr, primary);\n        }\n\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New {}selection for data source {:x}\", primary ? \"primary\" : \"\", (uintptr_t)source.get());\n\n    for (auto const& d : m_devices) {\n        sendSelectionToDevice(d, source, primary);\n    }\n}\n\nSP<CWLRDataDevice> CDataDeviceWLRProtocol::dataDeviceForClient(wl_client* c) {\n    auto it = std::ranges::find_if(m_devices, [c](const auto& e) { return e->client() == c; });\n    if (it == m_devices.end())\n        return nullptr;\n    return *it;\n}\n"
  },
  {
    "path": "src/protocols/DataDeviceWlr.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-data-control-unstable-v1.hpp\"\n#include \"types/DataDevice.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CWLRDataControlManagerResource;\nclass CWLRDataSource;\nclass CWLRDataDevice;\nclass CWLRDataOffer;\n\nclass CWLRDataOffer {\n  public:\n    CWLRDataOffer(SP<CZwlrDataControlOfferV1> resource_, SP<IDataSource> source);\n\n    bool            good();\n    void            sendData();\n\n    bool            m_dead    = false;\n    bool            m_primary = false;\n\n    WP<IDataSource> m_source;\n\n  private:\n    SP<CZwlrDataControlOfferV1> m_resource;\n\n    friend class CWLRDataDevice;\n};\n\nclass CWLRDataSource : public IDataSource {\n  public:\n    CWLRDataSource(SP<CZwlrDataControlSourceV1> resource_, SP<CWLRDataDevice> device_);\n    ~CWLRDataSource();\n    static SP<CWLRDataSource>        fromResource(wl_resource*);\n\n    bool                             good();\n\n    virtual std::vector<std::string> mimes();\n    virtual void                     send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd);\n    virtual void                     accepted(const std::string& mime);\n    virtual void                     cancelled();\n    virtual void                     error(uint32_t code, const std::string& msg);\n\n    std::vector<std::string>         m_mimeTypes;\n    WP<CWLRDataSource>               m_self;\n    WP<CWLRDataDevice>               m_device;\n\n  private:\n    SP<CZwlrDataControlSourceV1> m_resource;\n};\n\nclass CWLRDataDevice {\n  public:\n    CWLRDataDevice(SP<CZwlrDataControlDeviceV1> resource_);\n\n    bool               good();\n    wl_client*         client();\n    void               sendInitialSelections();\n\n    void               sendDataOffer(SP<CWLRDataOffer> offer);\n    void               sendSelection(SP<CWLRDataOffer> selection);\n    void               sendPrimarySelection(SP<CWLRDataOffer> selection);\n\n    WP<CWLRDataDevice> self;\n\n  private:\n    SP<CZwlrDataControlDeviceV1> m_resource;\n    wl_client*                   m_client = nullptr;\n\n    friend class CDataDeviceWLRProtocol;\n};\n\nclass CWLRDataControlManagerResource {\n  public:\n    CWLRDataControlManagerResource(SP<CZwlrDataControlManagerV1> resource_);\n\n    bool                            good();\n\n    WP<CWLRDataDevice>              m_device;\n    std::vector<WP<CWLRDataSource>> m_sources;\n\n  private:\n    SP<CZwlrDataControlManagerV1> m_resource;\n};\n\nclass CDataDeviceWLRProtocol : public IWaylandProtocol {\n  public:\n    CDataDeviceWLRProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CWLRDataControlManagerResource* resource);\n    void destroyResource(CWLRDataSource* resource);\n    void destroyResource(CWLRDataDevice* resource);\n    void destroyResource(CWLRDataOffer* resource);\n\n    //\n    std::vector<SP<CWLRDataControlManagerResource>> m_managers;\n    std::vector<SP<CWLRDataSource>>                 m_sources;\n    std::vector<SP<CWLRDataDevice>>                 m_devices;\n    std::vector<SP<CWLRDataOffer>>                  m_offers;\n\n    //\n    void setSelection(SP<IDataSource> source, bool primary);\n    void sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<IDataSource> sel, bool primary);\n\n    //\n    SP<CWLRDataDevice> dataDeviceForClient(wl_client*);\n\n    friend class CSeatManager;\n    friend class CWLRDataControlManagerResource;\n    friend class CWLRDataSource;\n    friend class CWLRDataDevice;\n    friend class CWLRDataOffer;\n};\n\nnamespace PROTO {\n    inline UP<CDataDeviceWLRProtocol> dataWlr;\n};\n"
  },
  {
    "path": "src/protocols/ExtDataDevice.cpp",
    "content": "#include \"ExtDataDevice.hpp\"\n#include <algorithm>\n#include \"../managers/SeatManager.hpp\"\n#include \"core/Seat.hpp\"\nusing namespace Hyprutils::OS;\n\nCExtDataOffer::CExtDataOffer(SP<CExtDataControlOfferV1> resource_, SP<IDataSource> source_) : m_source(source_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CExtDataControlOfferV1* r) { PROTO::extDataDevice->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtDataControlOfferV1* r) { PROTO::extDataDevice->destroyResource(this); });\n\n    m_resource->setReceive([this](CExtDataControlOfferV1* r, const char* mime, int32_t fd) {\n        CFileDescriptor sendFd{fd};\n        if (!m_source) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer w/o a source\");\n            return;\n        }\n\n        if (m_dead) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer that's dead\");\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"Offer {:x} asks to send data from source {:x}\", (uintptr_t)this, (uintptr_t)m_source.get());\n\n        m_source->send(mime, std::move(sendFd));\n    });\n}\n\nbool CExtDataOffer::good() {\n    return m_resource->resource();\n}\n\nvoid CExtDataOffer::sendData() {\n    if UNLIKELY (!m_source)\n        return;\n\n    for (auto const& m : m_source->mimes()) {\n        m_resource->sendOffer(m.c_str());\n    }\n}\n\nCExtDataSource::CExtDataSource(SP<CExtDataControlSourceV1> resource_, SP<CExtDataDevice> device_) : m_device(device_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CExtDataControlSourceV1* r) {\n        m_events.destroy.emit();\n        PROTO::extDataDevice->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CExtDataControlSourceV1* r) {\n        m_events.destroy.emit();\n        PROTO::extDataDevice->destroyResource(this);\n    });\n\n    m_resource->setOffer([this](CExtDataControlSourceV1* r, const char* mime) { m_mimeTypes.emplace_back(mime); });\n}\n\nCExtDataSource::~CExtDataSource() {\n    m_events.destroy.emit();\n}\n\nSP<CExtDataSource> CExtDataSource::fromResource(wl_resource* res) {\n    auto data = (CExtDataSource*)(((CExtDataControlSourceV1*)wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CExtDataSource::good() {\n    return m_resource->resource();\n}\n\nstd::vector<std::string> CExtDataSource::mimes() {\n    return m_mimeTypes;\n}\n\nvoid CExtDataSource::send(const std::string& mime, CFileDescriptor fd) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) {\n        LOGM(Log::ERR, \"Compositor/App bug: CExtDataSource::sendAskSend with non-existent mime\");\n        return;\n    }\n\n    m_resource->sendSend(mime.c_str(), fd.get());\n}\n\nvoid CExtDataSource::accepted(const std::string& mime) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end())\n        LOGM(Log::ERR, \"Compositor/App bug: CExtDataSource::sendAccepted with non-existent mime\");\n\n    // ext has no accepted\n}\n\nvoid CExtDataSource::cancelled() {\n    m_resource->sendCancelled();\n}\n\nvoid CExtDataSource::error(uint32_t code, const std::string& msg) {\n    m_resource->error(code, msg);\n}\n\nCExtDataDevice::CExtDataDevice(SP<CExtDataControlDeviceV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CExtDataControlDeviceV1* r) { PROTO::extDataDevice->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtDataControlDeviceV1* r) { PROTO::extDataDevice->destroyResource(this); });\n\n    m_resource->setSetSelection([](CExtDataControlDeviceV1* r, wl_resource* sourceR) {\n        auto source = sourceR ? CExtDataSource::fromResource(sourceR) : CSharedPointer<CExtDataSource>{};\n        if (!source) {\n            LOGM(Log::DEBUG, \"ext reset selection received\");\n            g_pSeatManager->setCurrentSelection(nullptr);\n            return;\n        }\n\n        if (source && source->used())\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        LOGM(Log::DEBUG, \"ext manager requests selection to {:x}\", (uintptr_t)source.get());\n        g_pSeatManager->setCurrentSelection(source);\n    });\n\n    m_resource->setSetPrimarySelection([](CExtDataControlDeviceV1* r, wl_resource* sourceR) {\n        auto source = sourceR ? CExtDataSource::fromResource(sourceR) : CSharedPointer<CExtDataSource>{};\n        if (!source) {\n            LOGM(Log::DEBUG, \"ext reset primary selection received\");\n            g_pSeatManager->setCurrentPrimarySelection(nullptr);\n            return;\n        }\n\n        if (source && source->used())\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        LOGM(Log::DEBUG, \"ext manager requests primary selection to {:x}\", (uintptr_t)source.get());\n        g_pSeatManager->setCurrentPrimarySelection(source);\n    });\n}\n\nbool CExtDataDevice::good() {\n    return m_resource->resource();\n}\n\nwl_client* CExtDataDevice::client() {\n    return m_client;\n}\n\nvoid CExtDataDevice::sendInitialSelections() {\n    PROTO::extDataDevice->sendSelectionToDevice(self.lock(), g_pSeatManager->m_selection.currentSelection.lock(), false);\n    PROTO::extDataDevice->sendSelectionToDevice(self.lock(), g_pSeatManager->m_selection.currentPrimarySelection.lock(), true);\n}\n\nvoid CExtDataDevice::sendDataOffer(SP<CExtDataOffer> offer) {\n    m_resource->sendDataOffer(offer->m_resource.get());\n}\n\nvoid CExtDataDevice::sendSelection(SP<CExtDataOffer> selection) {\n    m_resource->sendSelection(selection->m_resource.get());\n}\n\nvoid CExtDataDevice::sendPrimarySelection(SP<CExtDataOffer> selection) {\n    m_resource->sendPrimarySelection(selection->m_resource.get());\n}\n\nCExtDataControlManagerResource::CExtDataControlManagerResource(SP<CExtDataControlManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CExtDataControlManagerV1* r) { PROTO::extDataDevice->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtDataControlManagerV1* r) { PROTO::extDataDevice->destroyResource(this); });\n\n    m_resource->setGetDataDevice([this](CExtDataControlManagerV1* r, uint32_t id, wl_resource* seat) {\n        const auto RESOURCE = PROTO::extDataDevice->m_devices.emplace_back(makeShared<CExtDataDevice>(makeShared<CExtDataControlDeviceV1>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::extDataDevice->m_devices.pop_back();\n            return;\n        }\n\n        RESOURCE->self = RESOURCE;\n        m_device       = RESOURCE;\n\n        for (auto const& s : m_sources) {\n            if (!s)\n                continue;\n            s->m_device = RESOURCE;\n        }\n\n        RESOURCE->sendInitialSelections();\n\n        LOGM(Log::DEBUG, \"New ext data device bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n\n    m_resource->setCreateDataSource([this](CExtDataControlManagerV1* r, uint32_t id) {\n        std::erase_if(m_sources, [](const auto& e) { return e.expired(); });\n\n        const auto RESOURCE =\n            PROTO::extDataDevice->m_sources.emplace_back(makeShared<CExtDataSource>(makeShared<CExtDataControlSourceV1>(r->client(), r->version(), id), m_device.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::extDataDevice->m_sources.pop_back();\n            return;\n        }\n\n        if (!m_device)\n            LOGM(Log::WARN, \"New data source before a device was created\");\n\n        RESOURCE->m_self = RESOURCE;\n\n        m_sources.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New ext data source bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n}\n\nbool CExtDataControlManagerResource::good() {\n    return m_resource->resource();\n}\n\nCExtDataDeviceProtocol::CExtDataDeviceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CExtDataDeviceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CExtDataControlManagerResource>(makeShared<CExtDataControlManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New ext_data_control_manager at {:x}\", (uintptr_t)RESOURCE.get());\n}\n\nvoid CExtDataDeviceProtocol::destroyResource(CExtDataControlManagerResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CExtDataDeviceProtocol::destroyResource(CExtDataSource* resource) {\n    std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CExtDataDeviceProtocol::destroyResource(CExtDataDevice* resource) {\n    std::erase_if(m_devices, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CExtDataDeviceProtocol::destroyResource(CExtDataOffer* resource) {\n    std::erase_if(m_offers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CExtDataDeviceProtocol::sendSelectionToDevice(SP<CExtDataDevice> dev, SP<IDataSource> sel, bool primary) {\n    if (!sel) {\n        if (primary)\n            dev->m_resource->sendPrimarySelectionRaw(nullptr);\n        else\n            dev->m_resource->sendSelectionRaw(nullptr);\n        return;\n    }\n\n    const auto OFFER = m_offers.emplace_back(makeShared<CExtDataOffer>(makeShared<CExtDataControlOfferV1>(dev->m_resource->client(), dev->m_resource->version(), 0), sel));\n\n    if (!OFFER->good()) {\n        dev->m_resource->noMemory();\n        m_offers.pop_back();\n        return;\n    }\n\n    OFFER->m_primary = primary;\n\n    LOGM(Log::DEBUG, \"New {}offer {:x} for data source {:x}\", primary ? \"primary \" : \" \", (uintptr_t)OFFER.get(), (uintptr_t)sel.get());\n\n    dev->sendDataOffer(OFFER);\n    OFFER->sendData();\n    if (primary)\n        dev->sendPrimarySelection(OFFER);\n    else\n        dev->sendSelection(OFFER);\n}\n\nvoid CExtDataDeviceProtocol::setSelection(SP<IDataSource> source, bool primary) {\n    for (auto const& o : m_offers) {\n        if (o->m_source && o->m_source->hasDnd())\n            continue;\n        if (o->m_primary != primary)\n            continue;\n        o->m_dead = true;\n    }\n\n    if (!source) {\n        LOGM(Log::DEBUG, \"resetting {}selection\", primary ? \"primary \" : \" \");\n\n        for (auto const& d : m_devices) {\n            sendSelectionToDevice(d, nullptr, primary);\n        }\n\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New {}selection for data source {:x}\", primary ? \"primary\" : \"\", (uintptr_t)source.get());\n\n    for (auto const& d : m_devices) {\n        sendSelectionToDevice(d, source, primary);\n    }\n}\n\nSP<CExtDataDevice> CExtDataDeviceProtocol::dataDeviceForClient(wl_client* c) {\n    auto it = std::ranges::find_if(m_devices, [c](const auto& e) { return e->client() == c; });\n    if (it == m_devices.end())\n        return nullptr;\n    return *it;\n}\n"
  },
  {
    "path": "src/protocols/ExtDataDevice.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"ext-data-control-v1.hpp\"\n#include \"types/DataDevice.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CExtDataControlManagerResource;\nclass CExtDataSource;\nclass CExtDataDevice;\nclass CExtDataOffer;\n\nclass CExtDataOffer {\n  public:\n    CExtDataOffer(SP<CExtDataControlOfferV1> resource_, SP<IDataSource> source);\n\n    bool            good();\n    void            sendData();\n\n    bool            m_dead    = false;\n    bool            m_primary = false;\n\n    WP<IDataSource> m_source;\n\n  private:\n    SP<CExtDataControlOfferV1> m_resource;\n\n    friend class CExtDataDevice;\n};\n\nclass CExtDataSource : public IDataSource {\n  public:\n    CExtDataSource(SP<CExtDataControlSourceV1> resource_, SP<CExtDataDevice> device_);\n    ~CExtDataSource();\n    static SP<CExtDataSource>        fromResource(wl_resource*);\n\n    bool                             good();\n\n    virtual std::vector<std::string> mimes();\n    virtual void                     send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd);\n    virtual void                     accepted(const std::string& mime);\n    virtual void                     cancelled();\n    virtual void                     error(uint32_t code, const std::string& msg);\n\n    std::vector<std::string>         m_mimeTypes;\n    WP<CExtDataSource>               m_self;\n    WP<CExtDataDevice>               m_device;\n\n  private:\n    SP<CExtDataControlSourceV1> m_resource;\n};\n\nclass CExtDataDevice {\n  public:\n    CExtDataDevice(SP<CExtDataControlDeviceV1> resource_);\n\n    bool               good();\n    wl_client*         client();\n    void               sendInitialSelections();\n\n    void               sendDataOffer(SP<CExtDataOffer> offer);\n    void               sendSelection(SP<CExtDataOffer> selection);\n    void               sendPrimarySelection(SP<CExtDataOffer> selection);\n\n    WP<CExtDataDevice> self;\n\n  private:\n    SP<CExtDataControlDeviceV1> m_resource;\n    wl_client*                  m_client = nullptr;\n\n    friend class CExtDataDeviceProtocol;\n};\n\nclass CExtDataControlManagerResource {\n  public:\n    CExtDataControlManagerResource(SP<CExtDataControlManagerV1> resource_);\n\n    bool                            good();\n\n    WP<CExtDataDevice>              m_device;\n    std::vector<WP<CExtDataSource>> m_sources;\n\n  private:\n    SP<CExtDataControlManagerV1> m_resource;\n};\n\nclass CExtDataDeviceProtocol : public IWaylandProtocol {\n  public:\n    CExtDataDeviceProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CExtDataControlManagerResource* resource);\n    void destroyResource(CExtDataSource* resource);\n    void destroyResource(CExtDataDevice* resource);\n    void destroyResource(CExtDataOffer* resource);\n\n    //\n    std::vector<SP<CExtDataControlManagerResource>> m_managers;\n    std::vector<SP<CExtDataSource>>                 m_sources;\n    std::vector<SP<CExtDataDevice>>                 m_devices;\n    std::vector<SP<CExtDataOffer>>                  m_offers;\n\n    //\n    void setSelection(SP<IDataSource> source, bool primary);\n    void sendSelectionToDevice(SP<CExtDataDevice> dev, SP<IDataSource> sel, bool primary);\n\n    //\n    SP<CExtDataDevice> dataDeviceForClient(wl_client*);\n\n    friend class CSeatManager;\n    friend class CExtDataControlManagerResource;\n    friend class CExtDataSource;\n    friend class CExtDataDevice;\n    friend class CExtDataOffer;\n};\n\nnamespace PROTO {\n    inline UP<CExtDataDeviceProtocol> extDataDevice;\n};\n"
  },
  {
    "path": "src/protocols/ExtWorkspace.cpp",
    "content": "#include \"ExtWorkspace.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../event/EventBus.hpp\"\n#include <algorithm>\n#include <utility>\n#include \"core/Output.hpp\"\n\nCExtWorkspaceGroupResource::CExtWorkspaceGroupResource(WP<CExtWorkspaceManagerResource> manager, UP<CExtWorkspaceGroupHandleV1> resource, PHLMONITORREF monitor) :\n    m_monitor(std::move(monitor)), m_manager(std::move(manager)), m_resource(std::move(resource)) {\n    if (!good())\n        return;\n\n    m_resource->setData(this);\n    m_manager->m_resource->sendWorkspaceGroup(m_resource.get());\n\n    m_listeners.destroyed = m_monitor->m_events.destroy.listen([this] { m_resource->sendRemoved(); });\n\n    m_resource->setOnDestroy([this](auto) { PROTO::extWorkspace->destroyGroup(m_self); });\n    m_resource->setDestroy([this](auto) { PROTO::extWorkspace->destroyGroup(m_self); });\n\n    m_resource->sendCapabilities(sc<extWorkspaceGroupHandleV1GroupCapabilities>(0));\n\n    if (!PROTO::outputs.contains(m_monitor->m_name))\n        return;\n\n    const auto& output = PROTO::outputs.at(m_monitor->m_name);\n\n    if (auto resources = output->outputResourcesFrom(m_resource->client()); !resources.empty()) {\n        for (const auto& r : resources) {\n            m_resource->sendOutputEnter(r->getResource()->resource());\n        }\n    }\n\n    m_listeners.outputBound = output->m_events.outputBound.listen([this](const SP<CWLOutputResource>& output) {\n        if (output->client() != m_resource->client())\n            return;\n\n        m_resource->sendOutputEnter(output->getResource()->resource());\n\n        if (m_manager)\n            m_manager->scheduleDone();\n    });\n}\n\nbool CExtWorkspaceGroupResource::good() const {\n    return m_resource;\n}\n\nWP<CExtWorkspaceGroupResource> CExtWorkspaceGroupResource::fromResource(wl_resource* resource) {\n    auto handle = sc<CExtWorkspaceGroupHandleV1*>(wl_resource_get_user_data(resource))->data();\n    auto data   = sc<CExtWorkspaceGroupResource*>(handle);\n    return data ? data->m_self : WP<CExtWorkspaceGroupResource>();\n}\n\nvoid CExtWorkspaceGroupResource::sendToWorkspaces() {\n    m_manager->sendGroupToWorkspaces(m_self);\n    m_manager->scheduleDone();\n}\n\nvoid CExtWorkspaceGroupResource::workspaceEnter(const WP<CExtWorkspaceHandleV1>& handle) {\n    m_resource->sendWorkspaceEnter(handle.get());\n}\nvoid CExtWorkspaceGroupResource::workspaceLeave(const WP<CExtWorkspaceHandleV1>& handle) {\n    m_resource->sendWorkspaceLeave(handle.get());\n}\n\nCExtWorkspaceResource::CExtWorkspaceResource(WP<CExtWorkspaceManagerResource> manager, UP<CExtWorkspaceHandleV1> resource, PHLWORKSPACEREF workspace) :\n    m_manager(std::move(manager)), m_resource(std::move(resource)), m_workspace(std::move(workspace)) {\n    if (!good())\n        return;\n\n    m_resource->setData(this);\n    m_manager->m_resource->sendWorkspace(m_resource.get());\n\n    m_listeners.destroyed = m_workspace->m_events.destroy.listen([this] {\n        m_resource->sendRemoved();\n\n        if (m_manager)\n            m_manager->scheduleDone();\n    });\n\n    m_listeners.activeChanged = m_workspace->m_events.activeChanged.listen([this] {\n        sendState();\n        sendCapabilities();\n    });\n\n    m_listeners.monitorChanged = m_workspace->m_events.monitorChanged.listen([this] { this->sendGroup(); });\n\n    m_listeners.renamed = m_workspace->m_events.renamed.listen([this] {\n        m_resource->sendName(m_workspace->m_name.c_str());\n\n        if (m_manager)\n            m_manager->scheduleDone();\n    });\n\n    m_resource->setOnDestroy([this](auto) { PROTO::extWorkspace->destroyWorkspace(m_self); });\n    m_resource->setDestroy([this](auto) { PROTO::extWorkspace->destroyWorkspace(m_self); });\n\n    m_resource->setActivate([this](void*) { m_pendingState.activate = true; });\n    m_resource->setDeactivate([this](void*) { m_pendingState.deactivate = true; });\n\n    m_resource->setAssign([this](void*, wl_resource* groupResource) {\n        auto group = CExtWorkspaceGroupResource::fromResource(groupResource);\n\n        if (group)\n            m_pendingState.targetMonitor = group->m_monitor;\n    });\n\n    m_resource->sendName(m_workspace->m_name.c_str());\n\n    wl_array coordinates;\n    wl_array_init(&coordinates);\n\n    auto id = m_workspace->m_id;\n    if (id < 0 && !m_workspace->m_name.empty())\n        id += UINT32_MAX - 1337;\n\n    if (id > 0)\n        *sc<uint32_t*>(wl_array_add(&coordinates, sizeof(uint32_t))) = id;\n\n    m_resource->sendCoordinates(&coordinates);\n    wl_array_release(&coordinates);\n\n    sendState();\n    sendCapabilities();\n    sendGroup();\n\n    m_manager->scheduleDone();\n}\n\nbool CExtWorkspaceResource::good() const {\n    return m_resource;\n}\n\nbool CExtWorkspaceResource::isActive() const {\n    if (!m_workspace)\n        return false;\n\n    auto const& monitor      = m_workspace->m_monitor;\n    auto const& cmpWorkspace = m_workspace->m_isSpecialWorkspace ? monitor->m_activeSpecialWorkspace : monitor->m_activeWorkspace;\n    return m_workspace == cmpWorkspace;\n}\n\nvoid CExtWorkspaceResource::sendState() {\n    uint32_t state = 0;\n\n    if (isActive())\n        state |= EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE;\n\n    if (m_workspace->hasUrgentWindow())\n        state |= EXT_WORKSPACE_HANDLE_V1_STATE_URGENT;\n\n    if (m_workspace->m_isSpecialWorkspace)\n        state |= EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN;\n\n    m_resource->sendState(sc<extWorkspaceHandleV1State>(state));\n\n    if (m_manager)\n        m_manager->scheduleDone();\n}\n\nvoid CExtWorkspaceResource::sendCapabilities() {\n    uint32_t capabilities = EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN;\n    auto     active       = isActive();\n\n    if (!active)\n        capabilities |= EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE;\n\n    if (active && m_workspace->m_isSpecialWorkspace)\n        capabilities |= EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE;\n\n    m_resource->sendCapabilities(sc<extWorkspaceHandleV1WorkspaceCapabilities>(capabilities));\n\n    if (m_manager)\n        m_manager->scheduleDone();\n}\n\nvoid CExtWorkspaceResource::sendGroup() {\n    if (m_group)\n        m_group->workspaceLeave(m_resource);\n\n    if (m_manager) {\n        m_group = m_manager->findGroup(m_workspace->m_monitor);\n\n        if (m_group)\n            m_group->workspaceEnter(m_resource);\n\n        m_manager->scheduleDone();\n    }\n}\n\nvoid CExtWorkspaceResource::commit() {\n    // order is important\n\n    if (m_pendingState.deactivate && isActive() && m_workspace->m_isSpecialWorkspace)\n        m_workspace->m_monitor->setSpecialWorkspace(nullptr);\n\n    if (m_pendingState.targetMonitor && m_workspace && m_workspace->m_monitor != m_pendingState.targetMonitor)\n        g_pCompositor->moveWorkspaceToMonitor(m_workspace.lock(), m_pendingState.targetMonitor.lock(), true);\n\n    if (m_pendingState.activate && !isActive() && m_workspace)\n        m_workspace->m_monitor->changeWorkspace(m_workspace.lock());\n\n    m_pendingState.activate   = false;\n    m_pendingState.deactivate = false;\n    m_pendingState.targetMonitor.reset();\n}\n\nCExtWorkspaceManagerResource::CExtWorkspaceManagerResource(UP<CExtWorkspaceManagerV1> resource) : m_resource(std::move(resource)) {\n    if (!good())\n        return;\n\n    m_resource->setOnDestroy([this](auto) { PROTO::extWorkspace->destroyManager(m_self); });\n\n    m_resource->setStop([this](auto) {\n        m_resource->sendFinished();\n        PROTO::extWorkspace->destroyManager(m_self);\n    });\n\n    m_resource->setCommit([this](auto) {\n        for (auto& workspace : PROTO::extWorkspace->m_workspaces) {\n            if (workspace->m_manager == m_self)\n                workspace->commit();\n        }\n    });\n}\n\nvoid CExtWorkspaceManagerResource::init(WP<CExtWorkspaceManagerResource> self) {\n    if (!good())\n        return;\n\n    m_self = self;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        onMonitorCreated(m);\n    }\n\n    for (auto const& w : g_pCompositor->getWorkspaces()) {\n        onWorkspaceCreated(w.lock());\n    }\n}\n\nbool CExtWorkspaceManagerResource::good() const {\n    return m_resource;\n}\n\nvoid CExtWorkspaceManagerResource::scheduleDone() {\n    if (m_doneScheduled)\n        return;\n\n    m_doneScheduled = true;\n    g_pEventLoopManager->doLater([self = m_self] {\n        if (!self || !self->m_resource)\n            return;\n\n        self->m_doneScheduled = false;\n        self->m_resource->sendDone();\n    });\n}\n\nWP<CExtWorkspaceGroupResource> CExtWorkspaceManagerResource::findGroup(const PHLMONITORREF& monitor) const {\n    auto iter = std::ranges::find_if(PROTO::extWorkspace->m_groups,\n                                     [&](const UP<CExtWorkspaceGroupResource>& resource) { return resource->m_manager.get() == this && resource->m_monitor == monitor; });\n\n    return iter != PROTO::extWorkspace->m_groups.end() ? *iter : WP<CExtWorkspaceGroupResource>();\n}\n\nvoid CExtWorkspaceManagerResource::sendGroupToWorkspaces(const WP<CExtWorkspaceGroupResource>& group) {\n    for (auto& workspace : PROTO::extWorkspace->m_workspaces) {\n        if (workspace->m_manager == m_self && workspace->m_workspace && workspace->m_workspace->m_monitor == group->m_monitor)\n            workspace->sendGroup();\n    }\n}\n\nvoid CExtWorkspaceManagerResource::onMonitorCreated(const PHLMONITOR& monitor) {\n    auto& group = PROTO::extWorkspace->m_groups.emplace_back(\n        makeUnique<CExtWorkspaceGroupResource>(m_self, makeUnique<CExtWorkspaceGroupHandleV1>(m_resource->client(), m_resource->version(), 0), monitor));\n    group->m_self = group;\n    group->sendToWorkspaces();\n\n    if UNLIKELY (!group->good()) {\n        LOGM(Log::ERR, \"Couldn't create a workspace group object\");\n        wl_client_post_no_memory(m_resource->client());\n        return;\n    }\n\n    scheduleDone();\n}\n\nvoid CExtWorkspaceManagerResource::onWorkspaceCreated(const PHLWORKSPACE& workspace) {\n    auto& ws = PROTO::extWorkspace->m_workspaces.emplace_back(\n        makeUnique<CExtWorkspaceResource>(m_self, makeUnique<CExtWorkspaceHandleV1>(m_resource->client(), m_resource->version(), 0), workspace));\n    ws->m_self = ws;\n\n    if UNLIKELY (!ws->good()) {\n        LOGM(Log::ERR, \"Couldn't create a workspace object\");\n        wl_client_post_no_memory(m_resource->client());\n        return;\n    }\n}\n\nCExtWorkspaceProtocol::CExtWorkspaceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P1 = Event::bus()->m_events.workspace.created.listen([this](PHLWORKSPACEREF workspace) {\n        for (auto const& m : m_managers) {\n            m->onWorkspaceCreated(workspace.lock());\n        }\n    });\n\n    static auto P2 = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) {\n        for (auto const& m : m_managers) {\n            m->onMonitorCreated(monitor);\n        }\n    });\n}\n\nvoid CExtWorkspaceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    auto& manager = m_managers.emplace_back(makeUnique<CExtWorkspaceManagerResource>(makeUnique<CExtWorkspaceManagerV1>(client, ver, id)));\n    manager->init(manager);\n\n    if UNLIKELY (!manager->good()) {\n        LOGM(Log::ERR, \"Couldn't create a workspace manager\");\n        wl_client_post_no_memory(client);\n        return;\n    }\n}\n\nvoid CExtWorkspaceProtocol::destroyGroup(const WP<CExtWorkspaceGroupResource>& group) {\n    std::erase_if(m_groups, [&](const UP<CExtWorkspaceGroupResource>& resource) { return resource == group; });\n}\n\nvoid CExtWorkspaceProtocol::destroyWorkspace(const WP<CExtWorkspaceResource>& workspace) {\n    std::erase_if(m_workspaces, [&](const UP<CExtWorkspaceResource>& resource) { return resource == workspace; });\n}\n\nvoid CExtWorkspaceProtocol::destroyManager(const WP<CExtWorkspaceManagerResource>& manager) {\n    std::erase_if(PROTO::extWorkspace->m_managers, [&](const UP<CExtWorkspaceManagerResource>& resource) { return resource == manager; });\n}\n"
  },
  {
    "path": "src/protocols/ExtWorkspace.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"ext-workspace-v1.hpp\"\n#include <cstdint>\n#include <vector>\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/Monitor.hpp\"\n\nclass CExtWorkspaceManagerResource;\n\nclass CExtWorkspaceGroupResource {\n  public:\n    CExtWorkspaceGroupResource(WP<CExtWorkspaceManagerResource> manager, UP<CExtWorkspaceGroupHandleV1> resource, PHLMONITORREF monitor);\n\n    static WP<CExtWorkspaceGroupResource> fromResource(wl_resource*);\n\n    [[nodiscard]] bool                    good() const;\n\n    void                                  workspaceEnter(const WP<CExtWorkspaceHandleV1>&);\n    void                                  workspaceLeave(const WP<CExtWorkspaceHandleV1>&);\n\n    void                                  sendToWorkspaces();\n\n    PHLMONITORREF                         m_monitor;\n\n  private:\n    WP<CExtWorkspaceGroupResource>   m_self;\n    WP<CExtWorkspaceManagerResource> m_manager;\n    UP<CExtWorkspaceGroupHandleV1>   m_resource;\n\n    struct {\n        CHyprSignalListener destroyed;\n        CHyprSignalListener outputBound;\n    } m_listeners;\n\n    friend class CExtWorkspaceManagerResource;\n};\n\nclass CExtWorkspaceResource {\n  public:\n    CExtWorkspaceResource(WP<CExtWorkspaceManagerResource> manager, UP<CExtWorkspaceHandleV1> resource, PHLWORKSPACEREF workspace);\n\n    [[nodiscard]] bool good() const;\n\n    void               commit();\n\n  private:\n    WP<CExtWorkspaceResource>        m_self;\n    WP<CExtWorkspaceManagerResource> m_manager;\n    UP<CExtWorkspaceHandleV1>        m_resource;\n    WP<CExtWorkspaceGroupResource>   m_group;\n    PHLWORKSPACEREF                  m_workspace;\n\n    [[nodiscard]] bool               isActive() const;\n\n    void                             sendState();\n    void                             sendCapabilities();\n    void                             sendGroup();\n\n    struct {\n        bool          activate   = false;\n        bool          deactivate = false;\n        PHLMONITORREF targetMonitor;\n    } m_pendingState;\n\n    struct {\n        CHyprSignalListener destroyed;\n        CHyprSignalListener activeChanged;\n        CHyprSignalListener monitorChanged;\n        CHyprSignalListener renamed;\n    } m_listeners;\n\n    friend class CExtWorkspaceManagerResource;\n};\n\nclass CExtWorkspaceManagerResource {\n  public:\n    CExtWorkspaceManagerResource(UP<CExtWorkspaceManagerV1> resource);\n    WP<CExtWorkspaceManagerResource>             m_self;\n\n    void                                         init(WP<CExtWorkspaceManagerResource> self);\n    [[nodiscard]] bool                           good() const;\n\n    void                                         onMonitorCreated(const PHLMONITOR& monitor);\n    void                                         onWorkspaceCreated(const PHLWORKSPACE& workspace);\n\n    void                                         scheduleDone();\n    [[nodiscard]] WP<CExtWorkspaceGroupResource> findGroup(const PHLMONITORREF& monitor) const;\n    void                                         sendGroupToWorkspaces(const WP<CExtWorkspaceGroupResource>& group);\n\n    UP<CExtWorkspaceManagerV1>                   m_resource;\n\n  private:\n    bool m_doneScheduled = false;\n};\n\nclass CExtWorkspaceProtocol : public IWaylandProtocol {\n  public:\n    CExtWorkspaceProtocol(const wl_interface* iface, const int& var, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         destroyManager(const WP<CExtWorkspaceManagerResource>& manager);\n    void         destroyGroup(const WP<CExtWorkspaceGroupResource>& group);\n    void         destroyWorkspace(const WP<CExtWorkspaceResource>& workspace);\n\n  private:\n    std::vector<UP<CExtWorkspaceManagerResource>> m_managers;\n    std::vector<UP<CExtWorkspaceGroupResource>>   m_groups;\n    std::vector<UP<CExtWorkspaceResource>>        m_workspaces;\n\n    friend class CExtWorkspaceManagerResource;\n};\n\nnamespace PROTO {\n    inline UP<CExtWorkspaceProtocol> extWorkspace;\n}\n"
  },
  {
    "path": "src/protocols/Fifo.cpp",
    "content": "#include \"Fifo.hpp\"\n#include \"Compositor.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../event/EventBus.hpp\"\n\nCFifoResource::CFifoResource(UP<CWpFifoV1>&& resource_, SP<CWLSurfaceResource> surface) : m_resource(std::move(resource_)), m_surface(surface) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setData(this);\n    m_resource->setDestroy([this](CWpFifoV1* r) { PROTO::fifo->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpFifoV1* r) { PROTO::fifo->destroyResource(this); });\n\n    m_resource->setSetBarrier([this](CWpFifoV1* r) {\n        if (!m_surface) {\n            r->error(WP_FIFO_V1_ERROR_SURFACE_DESTROYED, \"Surface was gone\");\n            return;\n        }\n\n        m_surface->m_pending.barrierSet        = true;\n        m_surface->m_pending.updated.bits.fifo = true;\n    });\n\n    m_resource->setWaitBarrier([this](CWpFifoV1* r) {\n        if (!m_surface) {\n            r->error(WP_FIFO_V1_ERROR_SURFACE_DESTROYED, \"Surface was gone\");\n            return;\n        }\n\n        if (!m_surface->m_current.barrierSet) {\n            // that might mean an empty commit with a barrier_set alone\n            static const auto PPEND = CConfigValue<Hyprlang::INT>(\"debug:fifo_pending_workaround\");\n            if (!m_surface->m_pending.fifoScheduled)\n                m_surface->m_pending.fifoScheduled = checkMonitors(*PPEND);\n\n            return;\n        }\n\n        m_surface->m_pending.surfaceLocked = true;\n    });\n\n    m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) {\n        if (!state || !state->surfaceLocked)\n            return;\n\n        static const auto PPEND = CConfigValue<Hyprlang::INT>(\"debug:fifo_pending_workaround\");\n\n        //#TODO:\n        // this feels wrong, but if we have no pending frames, presented might never come because\n        // we are waiting on the barrier to unlock and no damage is around.\n        // unlock on timeout instead?\n        if (!state->fifoScheduled)\n            state->fifoScheduled = checkMonitors(*PPEND);\n\n        if (!state->fifoScheduled)\n            return;\n\n        // only lock once its mapped.\n        if (m_surface->m_mapped)\n            m_surface->m_stateQueue.lock(state, LOCK_REASON_FIFO);\n    });\n}\n\nCFifoResource::~CFifoResource() {\n    ;\n}\n\nbool CFifoResource::good() {\n    return m_resource->resource();\n}\n\nvoid CFifoResource::presented() {\n    m_surface->m_current.barrierSet = false;\n    m_surface->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);\n}\n\nbool CFifoResource::checkMonitors(bool needsSchedule) {\n    if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) {\n        for (auto& m : g_pCompositor->m_monitors) {\n            if (!m || !m->m_enabled)\n                continue;\n\n            auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal();\n            if (box && !box->intersection({m->m_position, m->m_size}).empty()) {\n                if (m->m_tearingState.activelyTearing)\n                    return false; // dont fifo lock on tearing.\n\n                if (needsSchedule)\n                    g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);\n            }\n        }\n    } else {\n        for (auto& m : m_surface->m_enteredOutputs) {\n            if (!m)\n                continue;\n\n            if (m->m_tearingState.activelyTearing)\n                return false; // dont fifo lock on tearing.\n\n            if (needsSchedule)\n                g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);\n        }\n    }\n\n    return true;\n}\n\nCFifoManagerResource::CFifoManagerResource(UP<CWpFifoManagerV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CWpFifoManagerV1* r) { PROTO::fifo->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpFifoManagerV1* r) { PROTO::fifo->destroyResource(this); });\n\n    m_resource->setGetFifo([](CWpFifoManagerV1* r, uint32_t id, wl_resource* surfResource) {\n        if (!surfResource) {\n            r->error(-1, \"No resource for fifo\");\n            return;\n        }\n\n        auto surf = CWLSurfaceResource::fromResource(surfResource);\n\n        if (!surf) {\n            r->error(-1, \"No surface for fifo\");\n            return;\n        }\n\n        if (surf->m_fifo) {\n            r->error(WP_FIFO_MANAGER_V1_ERROR_ALREADY_EXISTS, \"Surface already has a fifo\");\n            return;\n        }\n\n        const auto& RESOURCE = PROTO::fifo->m_fifos.emplace_back(makeUnique<CFifoResource>(makeUnique<CWpFifoV1>(r->client(), r->version(), id), surf));\n\n        if (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::fifo->m_fifos.pop_back();\n            return;\n        }\n\n        surf->m_fifo = RESOURCE;\n        LOGM(Log::DEBUG, \"New fifo at {:x} for surface {:x}\", (uintptr_t)RESOURCE, (uintptr_t)surf.get());\n    });\n}\n\nCFifoManagerResource::~CFifoManagerResource() {\n    ;\n}\n\nbool CFifoManagerResource::good() {\n    return m_resource->resource();\n}\n\nCFifoProtocol::CFifoProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) {\n        M->m_events.presented.listenStatic([this, m = PHLMONITORREF{M}]() {\n            if (!m || !PROTO::fifo)\n                return;\n\n            onMonitorPresent(m.lock());\n        });\n    });\n}\n\nvoid CFifoProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CFifoManagerResource>(makeUnique<CWpFifoManagerV1>(client, ver, id))).get();\n\n    if (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CFifoProtocol::destroyResource(CFifoManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n\nvoid CFifoProtocol::destroyResource(CFifoResource* res) {\n    std::erase_if(m_fifos, [&](const auto& other) { return other.get() == res; });\n}\n\nvoid CFifoProtocol::onMonitorPresent(PHLMONITOR m) {\n    if (m->m_tearingState.activelyTearing)\n        return; // fifo isnt locked on tearing.\n\n    for (const auto& fifo : m_fifos) {\n        if (!fifo->m_surface)\n            continue;\n\n        if (!fifo->m_surface->m_mapped) {\n            fifo->presented();\n            continue;\n        }\n\n        auto it = std::ranges::find_if(fifo->m_surface->m_enteredOutputs, [m](auto& mon) { return mon == m; });\n        if (it != fifo->m_surface->m_enteredOutputs.end()) {\n            fifo->presented();\n            continue;\n        }\n\n        if (fifo->m_surface->m_hlSurface) {\n            auto box = fifo->m_surface->m_hlSurface->getSurfaceBoxGlobal();\n            if (box && !box->intersection({m->m_position, m->m_size}).empty()) {\n                fifo->presented();\n                continue;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/protocols/Fifo.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"fifo-v1.hpp\"\n\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CFifoResource {\n  public:\n    CFifoResource(UP<CWpFifoV1>&& resource_, SP<CWLSurfaceResource> surface);\n    ~CFifoResource();\n\n    bool good();\n\n  private:\n    UP<CWpFifoV1>          m_resource;\n\n    WP<CWLSurfaceResource> m_surface;\n\n    struct {\n        CHyprSignalListener surfaceStateCommit;\n    } m_listeners;\n\n    void presented();\n    bool checkMonitors(bool needsSchedule = false);\n\n    friend class CFifoProtocol;\n    friend class CFifoManagerResource;\n};\n\nclass CFifoManagerResource {\n  public:\n    CFifoManagerResource(UP<CWpFifoManagerV1>&& resource_);\n    ~CFifoManagerResource();\n\n    bool good();\n\n  private:\n    UP<CWpFifoManagerV1> m_resource;\n};\n\nclass CFifoProtocol : public IWaylandProtocol {\n  public:\n    CFifoProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CFifoManagerResource* resource);\n    void destroyResource(CFifoResource* resource);\n\n    void onMonitorPresent(PHLMONITOR m);\n\n    //\n    std::vector<UP<CFifoManagerResource>> m_managers;\n    std::vector<UP<CFifoResource>>        m_fifos;\n\n    friend class CFifoManagerResource;\n    friend class CFifoResource;\n};\n\nnamespace PROTO {\n    inline UP<CFifoProtocol> fifo;\n};\n"
  },
  {
    "path": "src/protocols/FocusGrab.cpp",
    "content": "#include \"FocusGrab.hpp\"\n#include \"../Compositor.hpp\"\n#include <hyprland-focus-grab-v1.hpp>\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"core/Compositor.hpp\"\n#include <cstdint>\n#include <wayland-server.h>\n\nCFocusGrabSurfaceState::CFocusGrabSurfaceState(CFocusGrab* grab, SP<CWLSurfaceResource> surface) {\n    m_listeners.destroy = surface->m_events.destroy.listen([grab, surface] { grab->eraseSurface(surface); });\n}\n\nCFocusGrab::CFocusGrab(SP<CHyprlandFocusGrabV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_grab             = makeShared<CSeatGrab>();\n    m_grab->m_keyboard = true;\n    m_grab->m_pointer  = true;\n    m_grab->setCallback([this]() { finish(true); });\n\n    m_resource->setDestroy([this](CHyprlandFocusGrabV1* pMgr) { PROTO::focusGrab->destroyGrab(this); });\n    m_resource->setOnDestroy([this](CHyprlandFocusGrabV1* pMgr) { PROTO::focusGrab->destroyGrab(this); });\n    m_resource->setAddSurface([this](CHyprlandFocusGrabV1* pMgr, wl_resource* surface) { addSurface(CWLSurfaceResource::fromResource(surface)); });\n    m_resource->setRemoveSurface([this](CHyprlandFocusGrabV1* pMgr, wl_resource* surface) { removeSurface(CWLSurfaceResource::fromResource(surface)); });\n    m_resource->setCommit([this](CHyprlandFocusGrabV1* pMgr) { commit(); });\n}\n\nCFocusGrab::~CFocusGrab() {\n    finish(false);\n}\n\nbool CFocusGrab::good() {\n    return m_resource->resource();\n}\n\nbool CFocusGrab::isSurfaceCommitted(SP<CWLSurfaceResource> surface) {\n    auto iter = std::ranges::find_if(m_surfaces, [surface](const auto& o) { return o.first == surface; });\n    if (iter == m_surfaces.end())\n        return false;\n\n    return iter->second->m_state == CFocusGrabSurfaceState::Committed;\n}\n\nvoid CFocusGrab::start() {\n    if (!m_grabActive) {\n        m_grabActive = true;\n        g_pSeatManager->setGrab(m_grab);\n    }\n\n    // Ensure new surfaces are focused if under the mouse when committed.\n    g_pInputManager->simulateMouseMovement();\n    refocusKeyboard();\n}\n\nvoid CFocusGrab::finish(bool sendCleared) {\n    if (m_grabActive) {\n        m_grabActive = false;\n\n        if (g_pSeatManager->m_seatGrab == m_grab)\n            g_pSeatManager->setGrab(nullptr);\n\n        m_grab->clear();\n        m_surfaces.clear();\n\n        if (sendCleared)\n            m_resource->sendCleared();\n    }\n}\n\nvoid CFocusGrab::addSurface(SP<CWLSurfaceResource> surface) {\n    auto iter = std::ranges::find_if(m_surfaces, [surface](const auto& e) { return e.first == surface; });\n    if (iter == m_surfaces.end())\n        m_surfaces.emplace(surface, makeUnique<CFocusGrabSurfaceState>(this, surface));\n}\n\nvoid CFocusGrab::removeSurface(SP<CWLSurfaceResource> surface) {\n    auto iter = m_surfaces.find(surface);\n    if (iter != m_surfaces.end()) {\n        if (iter->second->m_state == CFocusGrabSurfaceState::PendingAddition)\n            m_surfaces.erase(iter);\n        else\n            iter->second->m_state = CFocusGrabSurfaceState::PendingRemoval;\n    }\n}\n\nvoid CFocusGrab::eraseSurface(SP<CWLSurfaceResource> surface) {\n    removeSurface(surface);\n    commit(true);\n}\n\nvoid CFocusGrab::refocusKeyboard() {\n    auto keyboardSurface = g_pSeatManager->m_state.keyboardFocus;\n    if (keyboardSurface && isSurfaceCommitted(keyboardSurface.lock()))\n        return;\n\n    SP<CWLSurfaceResource> surface = nullptr;\n    for (auto const& [surf, state] : m_surfaces) {\n        if (state->m_state == CFocusGrabSurfaceState::Committed) {\n            surface = surf.lock();\n            break;\n        }\n    }\n\n    if (surface)\n        Desktop::focusState()->rawSurfaceFocus(surface);\n    else\n        LOGM(Log::ERR, \"CFocusGrab::refocusKeyboard called with no committed surfaces. This should never happen.\");\n}\n\nvoid CFocusGrab::commit(bool removeOnly) {\n    auto surfacesChanged = false;\n    auto anyCommitted    = false;\n    for (auto iter = m_surfaces.begin(); iter != m_surfaces.end();) {\n        switch (iter->second->m_state) {\n            case CFocusGrabSurfaceState::PendingRemoval:\n                m_grab->remove(iter->first.lock());\n                iter            = m_surfaces.erase(iter);\n                surfacesChanged = true;\n                continue;\n            case CFocusGrabSurfaceState::PendingAddition:\n                if (!removeOnly) {\n                    iter->second->m_state = CFocusGrabSurfaceState::Committed;\n                    m_grab->add(iter->first.lock());\n                    surfacesChanged = true;\n                    anyCommitted    = true;\n                }\n                break;\n            case CFocusGrabSurfaceState::Committed: anyCommitted = true; break;\n        }\n\n        iter++;\n    }\n\n    if (surfacesChanged) {\n        if (anyCommitted)\n            start();\n        else\n            finish(true);\n    }\n}\n\nCFocusGrabProtocol::CFocusGrabProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CFocusGrabProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CHyprlandFocusGrabManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CHyprlandFocusGrabManagerV1* p) { onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CHyprlandFocusGrabManagerV1* p) { onManagerResourceDestroy(p->resource()); });\n    RESOURCE->setCreateGrab([this](CHyprlandFocusGrabManagerV1* pMgr, uint32_t id) { onCreateGrab(pMgr, id); });\n}\n\nvoid CFocusGrabProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CFocusGrabProtocol::destroyGrab(CFocusGrab* grab) {\n    std::erase_if(m_grabs, [&](const auto& other) { return other.get() == grab; });\n}\n\nvoid CFocusGrabProtocol::onCreateGrab(CHyprlandFocusGrabManagerV1* pMgr, uint32_t id) {\n    m_grabs.push_back(makeUnique<CFocusGrab>(makeShared<CHyprlandFocusGrabV1>(pMgr->client(), pMgr->version(), id)));\n    const auto RESOURCE = m_grabs.back().get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_grabs.pop_back();\n    }\n}\n"
  },
  {
    "path": "src/protocols/FocusGrab.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"hyprland-focus-grab-v1.hpp\"\n#include \"../macros.hpp\"\n#include <cstdint>\n#include <unordered_map>\n#include <vector>\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CFocusGrab;\nclass CSeatGrab;\nclass CWLSurfaceResource;\n\nclass CFocusGrabSurfaceState {\n  public:\n    CFocusGrabSurfaceState(CFocusGrab* grab, SP<CWLSurfaceResource> surface);\n    ~CFocusGrabSurfaceState() = default;\n\n    enum State {\n        PendingAddition,\n        PendingRemoval,\n        Committed,\n    } m_state = PendingAddition;\n\n  private:\n    struct {\n        CHyprSignalListener destroy;\n    } m_listeners;\n};\n\nclass CFocusGrab {\n  public:\n    CFocusGrab(SP<CHyprlandFocusGrabV1> resource_);\n    ~CFocusGrab();\n\n    bool good();\n    bool isSurfaceCommitted(SP<CWLSurfaceResource> surface);\n\n    void start();\n    void finish(bool sendCleared);\n\n  private:\n    void                                                                   addSurface(SP<CWLSurfaceResource> surface);\n    void                                                                   removeSurface(SP<CWLSurfaceResource> surface);\n    void                                                                   eraseSurface(SP<CWLSurfaceResource> surface);\n    void                                                                   refocusKeyboard();\n    void                                                                   commit(bool removeOnly = false);\n\n    SP<CHyprlandFocusGrabV1>                                               m_resource;\n    std::unordered_map<WP<CWLSurfaceResource>, UP<CFocusGrabSurfaceState>> m_surfaces;\n    SP<CSeatGrab>                                                          m_grab;\n\n    bool                                                                   m_grabActive = false;\n\n    friend class CFocusGrabSurfaceState;\n};\n\nclass CFocusGrabProtocol : public IWaylandProtocol {\n  public:\n    CFocusGrabProtocol(const wl_interface* iface, const int& var, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void                                         onManagerResourceDestroy(wl_resource* res);\n    void                                         destroyGrab(CFocusGrab* grab);\n    void                                         onCreateGrab(CHyprlandFocusGrabManagerV1* pMgr, uint32_t id);\n\n    std::vector<UP<CHyprlandFocusGrabManagerV1>> m_managers;\n    std::vector<UP<CFocusGrab>>                  m_grabs;\n\n    friend class CFocusGrab;\n};\n\nnamespace PROTO {\n    inline UP<CFocusGrabProtocol> focusGrab;\n}\n"
  },
  {
    "path": "src/protocols/ForeignToplevel.cpp",
    "content": "#include \"ForeignToplevel.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../event/EventBus.hpp\"\n\nCForeignToplevelHandle::CForeignToplevelHandle(SP<CExtForeignToplevelHandleV1> resource_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setOnDestroy([this](CExtForeignToplevelHandleV1* h) { PROTO::foreignToplevel->destroyHandle(this); });\n    m_resource->setDestroy([this](CExtForeignToplevelHandleV1* h) { PROTO::foreignToplevel->destroyHandle(this); });\n}\n\nbool CForeignToplevelHandle::good() {\n    return m_resource->resource();\n}\n\nPHLWINDOW CForeignToplevelHandle::window() {\n    return m_window.lock();\n}\n\nCForeignToplevelList::CForeignToplevelList(SP<CExtForeignToplevelListV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setOnDestroy([this](CExtForeignToplevelListV1* h) { PROTO::foreignToplevel->onManagerResourceDestroy(this); });\n    m_resource->setDestroy([this](CExtForeignToplevelListV1* h) { PROTO::foreignToplevel->onManagerResourceDestroy(this); });\n\n    m_resource->setStop([this](CExtForeignToplevelListV1* h) {\n        m_resource->sendFinished();\n        m_finished = true;\n        LOGM(Log::DEBUG, \"CForeignToplevelList: finished\");\n    });\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!PROTO::foreignToplevel->windowValidForForeign(w))\n            continue;\n\n        onMap(w);\n    }\n}\n\nvoid CForeignToplevelList::onMap(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    // check if the window already had a handle in the past\n    const auto OLDHANDLE = handleForWindow(pWindow);\n    if (OLDHANDLE) {\n        if (!OLDHANDLE->m_closed)\n            OLDHANDLE->m_resource->sendClosed();\n\n        std::erase_if(m_handles, [&](const auto& other) { return other.get() == OLDHANDLE.get(); });\n    }\n\n    auto newHandle = PROTO::foreignToplevel->m_handles.emplace_back(\n        makeShared<CForeignToplevelHandle>(makeShared<CExtForeignToplevelHandleV1>(m_resource->client(), m_resource->version(), 0), pWindow));\n\n    if (!newHandle->good()) {\n        LOGM(Log::ERR, \"Couldn't create a foreign handle\");\n        m_resource->noMemory();\n        PROTO::foreignToplevel->m_handles.pop_back();\n        return;\n    }\n\n    const auto IDENTIFIER = std::format(\"{:x}\", pWindow->m_stableID);\n\n    LOGM(Log::DEBUG, \"Newly mapped window gets an identifier of {}\", IDENTIFIER);\n    m_resource->sendToplevel(newHandle->m_resource.get());\n    newHandle->m_resource->sendIdentifier(IDENTIFIER.c_str());\n    newHandle->m_resource->sendAppId(pWindow->m_initialClass.c_str());\n    newHandle->m_resource->sendTitle(pWindow->m_initialTitle.c_str());\n    newHandle->m_resource->sendDone();\n\n    m_handles.emplace_back(std::move(newHandle));\n}\n\nSP<CForeignToplevelHandle> CForeignToplevelList::handleForWindow(PHLWINDOW pWindow) {\n    std::erase_if(m_handles, [](const auto& wp) { return wp.expired(); });\n    const auto IT = std::ranges::find_if(m_handles, [pWindow](const auto& h) { return h->window() == pWindow; });\n    return IT == m_handles.end() ? SP<CForeignToplevelHandle>{} : IT->lock();\n}\n\nvoid CForeignToplevelList::onTitle(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed)\n        return;\n\n    H->m_resource->sendTitle(pWindow->m_title.c_str());\n    H->m_resource->sendDone();\n}\n\nvoid CForeignToplevelList::onClass(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed)\n        return;\n\n    H->m_resource->sendAppId(pWindow->m_class.c_str());\n    H->m_resource->sendDone();\n}\n\nvoid CForeignToplevelList::onUnmap(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H)\n        return;\n\n    H->m_resource->sendClosed();\n    H->m_closed = true;\n}\n\nbool CForeignToplevelList::good() {\n    return m_resource->resource();\n}\n\nCForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onMap(window);\n        }\n    });\n\n    static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onUnmap(window);\n        }\n    });\n\n    static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onTitle(window);\n        }\n    });\n}\n\nvoid CForeignToplevelProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CForeignToplevelList>(makeShared<CExtForeignToplevelListV1>(client, ver, id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        LOGM(Log::ERR, \"Couldn't create a foreign list\");\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CForeignToplevelProtocol::onManagerResourceDestroy(CForeignToplevelList* mgr) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == mgr; });\n}\n\nvoid CForeignToplevelProtocol::destroyHandle(CForeignToplevelHandle* handle) {\n    std::erase_if(m_handles, [&](const auto& other) { return other.get() == handle; });\n}\n\nbool CForeignToplevelProtocol::windowValidForForeign(PHLWINDOW pWindow) {\n    return validMapped(pWindow) && !pWindow->isX11OverrideRedirect();\n}\n\nPHLWINDOW CForeignToplevelProtocol::windowFromHandleResource(wl_resource* res) {\n    auto data = sc<CForeignToplevelHandle*>(sc<CExtForeignToplevelHandleV1*>(wl_resource_get_user_data(res))->data());\n    return data ? data->window() : nullptr;\n}\n"
  },
  {
    "path": "src/protocols/ForeignToplevel.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"ext-foreign-toplevel-list-v1.hpp\"\n\nclass CForeignToplevelHandle {\n  public:\n    CForeignToplevelHandle(SP<CExtForeignToplevelHandleV1> resource_, PHLWINDOW pWindow);\n\n    bool      good();\n    PHLWINDOW window();\n\n  private:\n    SP<CExtForeignToplevelHandleV1> m_resource;\n    PHLWINDOWREF                    m_window;\n    bool                            m_closed = false;\n\n    friend class CForeignToplevelList;\n    friend class CForeignToplevelProtocol;\n};\n\nclass CForeignToplevelList {\n  public:\n    CForeignToplevelList(SP<CExtForeignToplevelListV1> resource_);\n\n    void onMap(PHLWINDOW pWindow);\n    void onTitle(PHLWINDOW pWindow);\n    void onClass(PHLWINDOW pWindow);\n    void onUnmap(PHLWINDOW pWindow);\n\n    bool good();\n\n  private:\n    SP<CExtForeignToplevelListV1>           m_resource;\n    bool                                    m_finished = false;\n\n    SP<CForeignToplevelHandle>              handleForWindow(PHLWINDOW pWindow);\n\n    std::vector<WP<CForeignToplevelHandle>> m_handles;\n};\n\nclass CForeignToplevelProtocol : public IWaylandProtocol {\n  public:\n    CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n    PHLWINDOW    windowFromHandleResource(wl_resource* res);\n\n  private:\n    void onManagerResourceDestroy(CForeignToplevelList* mgr);\n    void destroyHandle(CForeignToplevelHandle* handle);\n    bool windowValidForForeign(PHLWINDOW pWindow);\n\n    //\n    std::vector<UP<CForeignToplevelList>>   m_managers;\n    std::vector<SP<CForeignToplevelHandle>> m_handles;\n\n    friend class CForeignToplevelList;\n    friend class CForeignToplevelHandle;\n};\n\nnamespace PROTO {\n    inline UP<CForeignToplevelProtocol> foreignToplevel;\n};\n"
  },
  {
    "path": "src/protocols/ForeignToplevelWlr.cpp",
    "content": "#include \"ForeignToplevelWlr.hpp\"\n#include \"core/Output.hpp\"\n#include <algorithm>\n#include \"../Compositor.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../managers/EventManager.hpp\"\n#include \"../event/EventBus.hpp\"\n\nCForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SP<CZwlrForeignToplevelHandleV1> resource_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setOnDestroy([this](CZwlrForeignToplevelHandleV1* h) { PROTO::foreignToplevelWlr->destroyHandle(this); });\n    m_resource->setDestroy([this](CZwlrForeignToplevelHandleV1* h) { PROTO::foreignToplevelWlr->destroyHandle(this); });\n\n    m_resource->setActivate([this](CZwlrForeignToplevelHandleV1* p, wl_resource* seat) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        // these requests bypass the config'd stuff cuz it's usually like\n        // window switchers and shit\n        PWINDOW->activate(true);\n        g_pInputManager->simulateMouseMovement();\n    });\n\n    m_resource->setSetFullscreen([this](CZwlrForeignToplevelHandleV1* p, wl_resource* output) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN)\n            return;\n\n        if UNLIKELY (!PWINDOW->m_isMapped) {\n            PWINDOW->m_wantsInitialFullscreen = true;\n            return;\n        }\n\n        if (output) {\n            const auto wpMonitor = CWLOutputResource::fromResource(output)->m_monitor;\n\n            if (!wpMonitor.expired()) {\n                const auto monitor = wpMonitor.lock();\n\n                if (PWINDOW->m_workspace != monitor->m_activeWorkspace) {\n                    g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, monitor->m_activeWorkspace);\n                    Desktop::focusState()->rawMonitorFocus(monitor);\n                }\n            }\n        }\n\n        g_pCompositor->changeWindowFullscreenModeClient(PWINDOW, FSMODE_FULLSCREEN, true);\n        g_pHyprRenderer->damageWindow(PWINDOW);\n    });\n\n    m_resource->setUnsetFullscreen([this](CZwlrForeignToplevelHandleV1* p) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN)\n            return;\n\n        g_pCompositor->changeWindowFullscreenModeClient(PWINDOW, FSMODE_FULLSCREEN, false);\n    });\n\n    m_resource->setSetMaximized([this](CZwlrForeignToplevelHandleV1* p) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE)\n            return;\n\n        if UNLIKELY (!PWINDOW->m_isMapped) {\n            PWINDOW->m_wantsInitialFullscreen = true;\n            return;\n        }\n\n        g_pCompositor->changeWindowFullscreenModeClient(PWINDOW, FSMODE_MAXIMIZED, true);\n    });\n\n    m_resource->setUnsetMaximized([this](CZwlrForeignToplevelHandleV1* p) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE)\n            return;\n\n        g_pCompositor->changeWindowFullscreenModeClient(PWINDOW, FSMODE_MAXIMIZED, false);\n    });\n\n    m_resource->setSetMinimized([this](CZwlrForeignToplevelHandleV1* p) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        if UNLIKELY (!PWINDOW->m_isMapped)\n            return;\n\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"minimized\", .data = std::format(\"{:x},1\", rc<uintptr_t>(PWINDOW.get()))});\n    });\n\n    m_resource->setUnsetMinimized([this](CZwlrForeignToplevelHandleV1* p) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        if UNLIKELY (!PWINDOW->m_isMapped)\n            return;\n\n        g_pEventManager->postEvent(SHyprIPCEvent{.event = \"minimized\", .data = std::format(\"{:x},0\", rc<uintptr_t>(PWINDOW.get()))});\n    });\n\n    m_resource->setClose([this](CZwlrForeignToplevelHandleV1* p) {\n        const auto PWINDOW = m_window.lock();\n\n        if UNLIKELY (!PWINDOW)\n            return;\n\n        g_pCompositor->closeWindow(PWINDOW);\n    });\n}\n\nbool CForeignToplevelHandleWlr::good() {\n    return m_resource->resource();\n}\n\nPHLWINDOW CForeignToplevelHandleWlr::window() {\n    return m_window.lock();\n}\n\nwl_resource* CForeignToplevelHandleWlr::res() {\n    return m_resource->resource();\n}\n\nvoid CForeignToplevelHandleWlr::sendMonitor(PHLMONITOR pMonitor) {\n    if (m_lastMonitorID == pMonitor->m_id)\n        return;\n\n    const auto CLIENT = m_resource->client();\n\n    if (const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_lastMonitorID); PLASTMONITOR && PROTO::outputs.contains(PLASTMONITOR->m_name)) {\n        const auto OLDRESOURCES = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourcesFrom(CLIENT);\n\n        if LIKELY (!OLDRESOURCES.empty()) {\n            for (const auto& r : OLDRESOURCES) {\n                m_resource->sendOutputLeave(r->getResource()->resource());\n            }\n        }\n    }\n\n    if (PROTO::outputs.contains(pMonitor->m_name)) {\n        const auto NEWRESOURCES = PROTO::outputs.at(pMonitor->m_name)->outputResourcesFrom(CLIENT);\n\n        if LIKELY (!NEWRESOURCES.empty()) {\n            for (const auto& r : NEWRESOURCES) {\n                m_resource->sendOutputEnter(r->getResource()->resource());\n            }\n        }\n    }\n\n    m_lastMonitorID = pMonitor->m_id;\n}\n\nvoid CForeignToplevelHandleWlr::sendState() {\n    const auto PWINDOW = m_window.lock();\n\n    if UNLIKELY (!PWINDOW || !PWINDOW->m_workspace || !PWINDOW->m_isMapped)\n        return;\n\n    wl_array state;\n    wl_array_init(&state);\n\n    if (PWINDOW == Desktop::focusState()->window()) {\n        auto p = sc<uint32_t*>(wl_array_add(&state, sizeof(uint32_t)));\n        *p     = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED;\n    }\n\n    if (PWINDOW->isFullscreen()) {\n        auto p = sc<uint32_t*>(wl_array_add(&state, sizeof(uint32_t)));\n        if (PWINDOW->isEffectiveInternalFSMode(FSMODE_FULLSCREEN))\n            *p = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN;\n        else\n            *p = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED;\n    }\n\n    m_resource->sendState(&state);\n\n    wl_array_release(&state);\n}\n\nCForeignToplevelWlrManager::CForeignToplevelWlrManager(SP<CZwlrForeignToplevelManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setOnDestroy([this](CZwlrForeignToplevelManagerV1* h) { PROTO::foreignToplevelWlr->onManagerResourceDestroy(this); });\n\n    m_resource->setStop([this](CZwlrForeignToplevelManagerV1* h) {\n        m_resource->sendFinished();\n        m_finished = true;\n        LOGM(Log::DEBUG, \"CForeignToplevelWlrManager: finished\");\n        PROTO::foreignToplevelWlr->onManagerResourceDestroy(this);\n    });\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!PROTO::foreignToplevelWlr->windowValidForForeign(w))\n            continue;\n\n        onMap(w);\n    }\n\n    m_lastFocus = Desktop::focusState()->window();\n}\n\nvoid CForeignToplevelWlrManager::onMap(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto NEWHANDLE = PROTO::foreignToplevelWlr->m_handles.emplace_back(\n        makeShared<CForeignToplevelHandleWlr>(makeShared<CZwlrForeignToplevelHandleV1>(m_resource->client(), m_resource->version(), 0), pWindow));\n\n    if UNLIKELY (!NEWHANDLE->good()) {\n        LOGM(Log::ERR, \"Couldn't create a foreign handle\");\n        m_resource->noMemory();\n        PROTO::foreignToplevelWlr->m_handles.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"Newly mapped window {:016x}\", (uintptr_t)pWindow.get());\n    m_resource->sendToplevel(NEWHANDLE->m_resource.get());\n    NEWHANDLE->m_resource->sendAppId(pWindow->m_class.c_str());\n    NEWHANDLE->m_resource->sendTitle(pWindow->m_title.c_str());\n    if LIKELY (const auto PMONITOR = pWindow->m_monitor.lock(); PMONITOR)\n        NEWHANDLE->sendMonitor(PMONITOR);\n    NEWHANDLE->sendState();\n    NEWHANDLE->m_resource->sendDone();\n\n    m_handles.push_back(NEWHANDLE);\n}\n\nSP<CForeignToplevelHandleWlr> CForeignToplevelWlrManager::handleForWindow(PHLWINDOW pWindow) {\n    std::erase_if(m_handles, [](const auto& wp) { return wp.expired(); });\n    const auto IT = std::ranges::find_if(m_handles, [pWindow](const auto& h) { return h->window() == pWindow; });\n    return IT == m_handles.end() ? SP<CForeignToplevelHandleWlr>{} : IT->lock();\n}\n\nvoid CForeignToplevelWlrManager::onTitle(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed)\n        return;\n\n    H->m_resource->sendTitle(pWindow->m_title.c_str());\n    H->m_resource->sendDone();\n}\n\nvoid CForeignToplevelWlrManager::onClass(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed)\n        return;\n\n    H->m_resource->sendAppId(pWindow->m_class.c_str());\n    H->m_resource->sendDone();\n}\n\nvoid CForeignToplevelWlrManager::onUnmap(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H)\n        return;\n\n    H->m_resource->sendClosed();\n    H->m_resource->sendDone();\n    H->m_closed = true;\n}\n\nvoid CForeignToplevelWlrManager::onMoveMonitor(PHLWINDOW pWindow, PHLMONITOR pMonitor) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed || !pMonitor)\n        return;\n\n    H->sendMonitor(pMonitor);\n    H->m_resource->sendDone();\n}\n\nvoid CForeignToplevelWlrManager::onFullscreen(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed)\n        return;\n\n    H->sendState();\n    H->m_resource->sendDone();\n}\n\nvoid CForeignToplevelWlrManager::onNewFocus(PHLWINDOW pWindow) {\n    if UNLIKELY (m_finished)\n        return;\n\n    if LIKELY (const auto HOLD = handleForWindow(m_lastFocus.lock()); HOLD) {\n        HOLD->sendState();\n        HOLD->m_resource->sendDone();\n    }\n\n    m_lastFocus = pWindow;\n\n    const auto H = handleForWindow(pWindow);\n    if UNLIKELY (!H || H->m_closed)\n        return;\n\n    H->sendState();\n    H->m_resource->sendDone();\n}\n\nbool CForeignToplevelWlrManager::good() {\n    return m_resource->resource();\n}\n\nCForeignToplevelWlrProtocol::CForeignToplevelWlrProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onMap(window);\n        }\n    });\n\n    static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onUnmap(window);\n        }\n    });\n\n    static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onTitle(window);\n        }\n    });\n\n    static auto P3 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, Desktop::eFocusReason reason) {\n        if (window && !windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onNewFocus(window);\n        }\n    });\n\n    static auto P4 = Event::bus()->m_events.window.moveToWorkspace.listen([this](PHLWINDOW window, PHLWORKSPACE ws) {\n        if (!ws)\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onMoveMonitor(window, ws->m_monitor.lock());\n        }\n    });\n\n    static auto P5 = Event::bus()->m_events.window.fullscreen.listen([this](PHLWINDOW window) {\n        if (!windowValidForForeign(window))\n            return;\n\n        for (auto const& m : m_managers) {\n            m->onFullscreen(window);\n        }\n    });\n}\n\nvoid CForeignToplevelWlrProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CForeignToplevelWlrManager>(makeShared<CZwlrForeignToplevelManagerV1>(client, ver, id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        LOGM(Log::ERR, \"Couldn't create a foreign list\");\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CForeignToplevelWlrProtocol::onManagerResourceDestroy(CForeignToplevelWlrManager* mgr) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == mgr; });\n}\n\nvoid CForeignToplevelWlrProtocol::destroyHandle(CForeignToplevelHandleWlr* handle) {\n    std::erase_if(m_handles, [&](const auto& other) { return other.get() == handle; });\n}\n\nPHLWINDOW CForeignToplevelWlrProtocol::windowFromHandleResource(wl_resource* res) {\n    auto data = sc<CForeignToplevelHandleWlr*>(sc<CZwlrForeignToplevelHandleV1*>(wl_resource_get_user_data(res))->data());\n    return data ? data->window() : nullptr;\n}\n\nbool CForeignToplevelWlrProtocol::windowValidForForeign(PHLWINDOW pWindow) {\n    return validMapped(pWindow) && !pWindow->isX11OverrideRedirect();\n}\n"
  },
  {
    "path": "src/protocols/ForeignToplevelWlr.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-foreign-toplevel-management-unstable-v1.hpp\"\n\nclass CMonitor;\n\nclass CForeignToplevelHandleWlr {\n  public:\n    CForeignToplevelHandleWlr(SP<CZwlrForeignToplevelHandleV1> resource_, PHLWINDOW pWindow);\n\n    bool         good();\n    PHLWINDOW    window();\n    wl_resource* res();\n\n  private:\n    SP<CZwlrForeignToplevelHandleV1> m_resource;\n    PHLWINDOWREF                     m_window;\n    bool                             m_closed        = false;\n    MONITORID                        m_lastMonitorID = MONITOR_INVALID;\n\n    void                             sendMonitor(PHLMONITOR pMonitor);\n    void                             sendState();\n\n    friend class CForeignToplevelWlrManager;\n};\n\nclass CForeignToplevelWlrManager {\n  public:\n    CForeignToplevelWlrManager(SP<CZwlrForeignToplevelManagerV1> resource_);\n\n    void onMap(PHLWINDOW pWindow);\n    void onTitle(PHLWINDOW pWindow);\n    void onClass(PHLWINDOW pWindow);\n    void onMoveMonitor(PHLWINDOW pWindow, PHLMONITOR pMonitor);\n    void onFullscreen(PHLWINDOW pWindow);\n    void onNewFocus(PHLWINDOW pWindow);\n    void onUnmap(PHLWINDOW pWindow);\n\n    bool good();\n\n  private:\n    SP<CZwlrForeignToplevelManagerV1>          m_resource;\n    bool                                       m_finished = false;\n    PHLWINDOWREF                               m_lastFocus; // READ-ONLY\n\n    SP<CForeignToplevelHandleWlr>              handleForWindow(PHLWINDOW pWindow);\n\n    std::vector<WP<CForeignToplevelHandleWlr>> m_handles;\n};\n\nclass CForeignToplevelWlrProtocol : public IWaylandProtocol {\n  public:\n    CForeignToplevelWlrProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    PHLWINDOW    windowFromHandleResource(wl_resource* res);\n\n  private:\n    void onManagerResourceDestroy(CForeignToplevelWlrManager* mgr);\n    void destroyHandle(CForeignToplevelHandleWlr* handle);\n    bool windowValidForForeign(PHLWINDOW pWindow);\n\n    //\n    std::vector<UP<CForeignToplevelWlrManager>> m_managers;\n    std::vector<SP<CForeignToplevelHandleWlr>>  m_handles;\n\n    friend class CForeignToplevelWlrManager;\n    friend class CForeignToplevelHandleWlr;\n};\n\nnamespace PROTO {\n    inline UP<CForeignToplevelWlrProtocol> foreignToplevelWlr;\n};\n"
  },
  {
    "path": "src/protocols/FractionalScale.cpp",
    "content": "#include \"FractionalScale.hpp\"\n#include <algorithm>\n#include \"core/Compositor.hpp\"\n\nCFractionalScaleProtocol::CFractionalScaleProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CFractionalScaleProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CWpFractionalScaleManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CWpFractionalScaleManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CWpFractionalScaleManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetFractionalScale(\n        [this](CWpFractionalScaleManagerV1* pMgr, uint32_t id, wl_resource* surface) { this->onGetFractionalScale(pMgr, id, CWLSurfaceResource::fromResource(surface)); });\n}\n\nvoid CFractionalScaleProtocol::removeAddon(CFractionalScaleAddon* addon) {\n    m_addons.erase(addon->surf());\n}\n\nvoid CFractionalScaleProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [res](const auto& other) { return other->resource() == res; });\n}\n\nvoid CFractionalScaleProtocol::onGetFractionalScale(CWpFractionalScaleManagerV1* pMgr, uint32_t id, SP<CWLSurfaceResource> surface) {\n    for (auto const& [k, v] : m_addons) {\n        if (k == surface) {\n            LOGM(Log::ERR, \"Surface {:x} already has a fractionalScale addon\", (uintptr_t)surface.get());\n            pMgr->error(WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS, \"Fractional scale already exists\");\n            return;\n        }\n    }\n\n    const auto PADDON =\n        m_addons.emplace(surface, makeUnique<CFractionalScaleAddon>(makeShared<CWpFractionalScaleV1>(pMgr->client(), pMgr->version(), id), surface)).first->second.get();\n\n    if UNLIKELY (!PADDON->good()) {\n        m_addons.erase(surface);\n        pMgr->noMemory();\n        return;\n    }\n\n    PADDON->m_resource->setOnDestroy([this, PADDON](CWpFractionalScaleV1* self) { this->removeAddon(PADDON); });\n    PADDON->m_resource->setDestroy([this, PADDON](CWpFractionalScaleV1* self) { this->removeAddon(PADDON); });\n\n    if (std::ranges::find_if(m_surfaceScales, [surface](const auto& e) { return e.first == surface; }) == m_surfaceScales.end())\n        m_surfaceScales.emplace(surface, 1.F);\n\n    if (surface->m_mapped)\n        PADDON->setScale(m_surfaceScales.at(surface));\n\n    // clean old\n    std::erase_if(m_surfaceScales, [](const auto& e) { return e.first.expired(); });\n}\n\nvoid CFractionalScaleProtocol::sendScale(SP<CWLSurfaceResource> surf, const float& scale) {\n    m_surfaceScales[surf] = scale;\n    if (m_addons.contains(surf))\n        m_addons[surf]->setScale(scale);\n}\n\nCFractionalScaleAddon::CFractionalScaleAddon(SP<CWpFractionalScaleV1> resource_, SP<CWLSurfaceResource> surf_) : m_resource(resource_), m_surface(surf_) {\n    m_resource->setDestroy([this](CWpFractionalScaleV1* self) { PROTO::fractional->removeAddon(this); });\n    m_resource->setOnDestroy([this](CWpFractionalScaleV1* self) { PROTO::fractional->removeAddon(this); });\n}\n\nvoid CFractionalScaleAddon::setScale(const float& scale) {\n    if (m_scale == scale)\n        return;\n\n    m_scale = scale;\n    m_resource->sendPreferredScale(std::round(scale * 120.0));\n}\n\nbool CFractionalScaleAddon::good() {\n    return m_resource->resource();\n}\n\nSP<CWLSurfaceResource> CFractionalScaleAddon::surf() {\n    return m_surface.lock();\n}\n"
  },
  {
    "path": "src/protocols/FractionalScale.hpp",
    "content": "#pragma once\n\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"fractional-scale-v1.hpp\"\n\nclass CFractionalScaleProtocol;\nclass CWLSurfaceResource;\n\nclass CFractionalScaleAddon {\n  public:\n    CFractionalScaleAddon(SP<CWpFractionalScaleV1> resource_, SP<CWLSurfaceResource> surf_);\n\n    void                   setScale(const float& scale);\n\n    bool                   good();\n\n    SP<CWLSurfaceResource> surf();\n\n    bool                   operator==(const wl_resource* other) const {\n        return other == m_resource->resource();\n    }\n\n    bool operator==(const CFractionalScaleAddon* other) const {\n        return other->m_resource == m_resource;\n    }\n\n  private:\n    SP<CWpFractionalScaleV1> m_resource;\n    float                    m_scale = -1.F; // unset\n    WP<CWLSurfaceResource>   m_surface;\n\n    friend class CFractionalScaleProtocol;\n};\n\nclass CFractionalScaleProtocol : public IWaylandProtocol {\n  public:\n    CFractionalScaleProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         onSurfaceDestroy(SP<CWLSurfaceResource> surf);\n    void         sendScale(SP<CWLSurfaceResource> surf, const float& scale);\n\n  private:\n    void removeAddon(CFractionalScaleAddon*);\n    void onManagerResourceDestroy(wl_resource* res);\n    void onGetFractionalScale(CWpFractionalScaleManagerV1* pMgr, uint32_t id, SP<CWLSurfaceResource> surface);\n\n    //\n\n    std::unordered_map<WP<CWLSurfaceResource>, float>                     m_surfaceScales;\n    std::unordered_map<WP<CWLSurfaceResource>, UP<CFractionalScaleAddon>> m_addons;\n    std::vector<UP<CWpFractionalScaleManagerV1>>                          m_managers;\n\n    friend class CFractionalScaleAddon;\n};\n\nnamespace PROTO {\n    inline UP<CFractionalScaleProtocol> fractional;\n};\n"
  },
  {
    "path": "src/protocols/GammaControl.cpp",
    "content": "#include \"GammaControl.hpp\"\n#include <fcntl.h>\n#include <unistd.h>\n#include \"../helpers/Monitor.hpp\"\n#include \"../protocols/core/Output.hpp\"\n#include \"../render/Renderer.hpp\"\nusing namespace Hyprutils::OS;\n\nCGammaControl::CGammaControl(SP<CZwlrGammaControlV1> resource_, wl_resource* output) : m_resource(resource_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    auto OUTPUTRES = CWLOutputResource::fromResource(output);\n\n    if UNLIKELY (!OUTPUTRES) {\n        LOGM(Log::ERR, \"No output in CGammaControl\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    m_monitor = OUTPUTRES->m_monitor;\n\n    if UNLIKELY (!m_monitor || !m_monitor->m_output) {\n        LOGM(Log::ERR, \"No CMonitor\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    for (auto const& g : PROTO::gamma->m_gammaControllers) {\n        if UNLIKELY (g->m_monitor == m_monitor) {\n            m_resource->sendFailed();\n            return;\n        }\n    }\n\n    m_gammaSize = m_monitor->m_output->getGammaSize();\n\n    if UNLIKELY (m_gammaSize <= 0) {\n        LOGM(Log::ERR, \"Output {} doesn't support gamma\", m_monitor->m_name);\n        m_resource->sendFailed();\n        return;\n    }\n\n    m_gammaTable.resize(m_gammaSize * 3);\n\n    m_resource->setDestroy([this](CZwlrGammaControlV1* gamma) { PROTO::gamma->destroyGammaControl(this); });\n    m_resource->setOnDestroy([this](CZwlrGammaControlV1* gamma) { PROTO::gamma->destroyGammaControl(this); });\n\n    m_resource->setSetGamma([this](CZwlrGammaControlV1* gamma, int32_t fd) {\n        CFileDescriptor gammaFd{fd};\n        if UNLIKELY (!m_monitor) {\n            LOGM(Log::ERR, \"setGamma for a dead monitor\");\n            m_resource->sendFailed();\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"setGamma for {}\", m_monitor->m_name);\n\n        if UNLIKELY (m_monitor->gammaRampsInUse()) {\n            LOGM(Log::ERR, \"Monitor has gamma ramps in use (ICC?)\");\n            m_resource->sendFailed();\n            return;\n        }\n\n        // TODO: make CFileDescriptor getflags use F_GETFL\n        int fdFlags = fcntl(gammaFd.get(), F_GETFL, 0);\n        if UNLIKELY (fdFlags < 0) {\n            LOGM(Log::ERR, \"Failed to get fd flags\");\n            m_resource->sendFailed();\n            return;\n        }\n\n        // TODO: make CFileDescriptor setflags use F_SETFL\n        if UNLIKELY (fcntl(gammaFd.get(), F_SETFL, fdFlags | O_NONBLOCK) < 0) {\n            LOGM(Log::ERR, \"Failed to set fd flags\");\n            m_resource->sendFailed();\n            return;\n        }\n\n        ssize_t readBytes = read(gammaFd.get(), m_gammaTable.data(), m_gammaTable.size() * sizeof(uint16_t));\n\n        ssize_t moreBytes = 0;\n        {\n            const size_t BUF_SIZE      = 1;\n            char         buf[BUF_SIZE] = {};\n            moreBytes                  = read(gammaFd.get(), buf, BUF_SIZE);\n        }\n\n        if (readBytes < 0 || sc<size_t>(readBytes) != m_gammaTable.size() * sizeof(uint16_t) || moreBytes != 0) {\n            LOGM(Log::ERR, \"Failed to read bytes\");\n\n            if (sc<size_t>(readBytes) != m_gammaTable.size() * sizeof(uint16_t) || moreBytes > 0) {\n                gamma->error(ZWLR_GAMMA_CONTROL_V1_ERROR_INVALID_GAMMA, \"Gamma ramps size mismatch\");\n                return;\n            }\n\n            m_resource->sendFailed();\n            return;\n        }\n\n        m_gammaTableSet = true;\n\n        // translate the table to AQ format\n        std::vector<uint16_t> red, green, blue;\n        red.resize(m_gammaTable.size() / 3);\n        green.resize(m_gammaTable.size() / 3);\n        blue.resize(m_gammaTable.size() / 3);\n        for (size_t i = 0; i < m_gammaTable.size() / 3; ++i) {\n            red.at(i)   = m_gammaTable.at(i);\n            green.at(i) = m_gammaTable.at(m_gammaTable.size() / 3 + i);\n            blue.at(i)  = m_gammaTable.at((m_gammaTable.size() / 3) * 2 + i);\n        }\n\n        for (size_t i = 0; i < m_gammaTable.size() / 3; ++i) {\n            m_gammaTable.at(i * 3)     = red.at(i);\n            m_gammaTable.at(i * 3 + 1) = green.at(i);\n            m_gammaTable.at(i * 3 + 2) = blue.at(i);\n        }\n\n        applyToMonitor();\n    });\n\n    m_resource->sendGammaSize(m_gammaSize);\n\n    m_listeners.monitorDestroy    = m_monitor->m_events.destroy.listen([this] { this->onMonitorDestroy(); });\n    m_listeners.monitorDisconnect = m_monitor->m_events.disconnect.listen([this] { this->onMonitorDestroy(); });\n}\n\nCGammaControl::~CGammaControl() {\n    if (!m_gammaTableSet || !m_monitor || !m_monitor->m_output)\n        return;\n\n    // reset the LUT if the client dies for whatever reason and doesn't unset the gamma\n    m_monitor->m_output->state->setGammaLut({});\n}\n\nbool CGammaControl::good() {\n    return m_resource->resource();\n}\n\nvoid CGammaControl::applyToMonitor() {\n    if UNLIKELY (!m_monitor || !m_monitor->m_output)\n        return; // ??\n\n    LOGM(Log::DEBUG, \"setting to monitor {}\", m_monitor->m_name);\n\n    if (!m_gammaTableSet) {\n        m_monitor->m_output->state->setGammaLut({});\n        return;\n    }\n\n    m_monitor->m_output->state->setGammaLut(m_gammaTable);\n\n    if (!m_monitor->m_state.test()) {\n        LOGM(Log::DEBUG, \"setting to monitor {} failed\", m_monitor->m_name);\n        m_monitor->m_output->state->setGammaLut({});\n    }\n\n    g_pHyprRenderer->damageMonitor(m_monitor.lock());\n}\n\nPHLMONITOR CGammaControl::getMonitor() {\n    return m_monitor ? m_monitor.lock() : nullptr;\n}\n\nvoid CGammaControl::onMonitorDestroy() {\n    LOGM(Log::DEBUG, \"Destroying gamma control for {}\", m_monitor->m_name);\n    m_resource->sendFailed();\n}\n\nCGammaControlProtocol::CGammaControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CGammaControlProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwlrGammaControlManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwlrGammaControlManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwlrGammaControlManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetGammaControl([this](CZwlrGammaControlManagerV1* pMgr, uint32_t id, wl_resource* output) { this->onGetGammaControl(pMgr, id, output); });\n}\n\nvoid CGammaControlProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CGammaControlProtocol::destroyGammaControl(CGammaControl* gamma) {\n    std::erase_if(m_gammaControllers, [&](const auto& other) { return other.get() == gamma; });\n}\n\nvoid CGammaControlProtocol::onGetGammaControl(CZwlrGammaControlManagerV1* pMgr, uint32_t id, wl_resource* output) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_gammaControllers.emplace_back(makeUnique<CGammaControl>(makeShared<CZwlrGammaControlV1>(CLIENT, pMgr->version(), id), output)).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_gammaControllers.pop_back();\n        return;\n    }\n}\n\nvoid CGammaControlProtocol::applyGammaToState(PHLMONITOR pMonitor) {\n    for (auto const& g : m_gammaControllers) {\n        if (g->getMonitor() != pMonitor)\n            continue;\n\n        g->applyToMonitor();\n        break;\n    }\n}\n"
  },
  {
    "path": "src/protocols/GammaControl.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-gamma-control-unstable-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CMonitor;\n\nclass CGammaControl {\n  public:\n    CGammaControl(SP<CZwlrGammaControlV1> resource_, wl_resource* output);\n    ~CGammaControl();\n\n    bool       good();\n    void       applyToMonitor();\n    PHLMONITOR getMonitor();\n\n  private:\n    SP<CZwlrGammaControlV1> m_resource;\n    PHLMONITORREF           m_monitor;\n    size_t                  m_gammaSize     = 0;\n    bool                    m_gammaTableSet = false;\n    std::vector<uint16_t>   m_gammaTable; // [r,g,b]+\n\n    void                    onMonitorDestroy();\n\n    struct {\n        CHyprSignalListener monitorDisconnect;\n        CHyprSignalListener monitorDestroy;\n    } m_listeners;\n};\n\nclass CGammaControlProtocol : public IWaylandProtocol {\n  public:\n    CGammaControlProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         applyGammaToState(PHLMONITOR pMonitor);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyGammaControl(CGammaControl* gamma);\n    void onGetGammaControl(CZwlrGammaControlManagerV1* pMgr, uint32_t id, wl_resource* output);\n\n    //\n    std::vector<UP<CZwlrGammaControlManagerV1>> m_managers;\n    std::vector<UP<CGammaControl>>              m_gammaControllers;\n\n    friend class CGammaControl;\n};\n\nnamespace PROTO {\n    inline UP<CGammaControlProtocol> gamma;\n};\n"
  },
  {
    "path": "src/protocols/GlobalShortcuts.cpp",
    "content": "#include \"GlobalShortcuts.hpp\"\n#include \"../helpers/time/Time.hpp\"\n\nCShortcutClient::CShortcutClient(SP<CHyprlandGlobalShortcutsManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CHyprlandGlobalShortcutsManagerV1* pMgr) { PROTO::globalShortcuts->destroyResource(this); });\n    m_resource->setDestroy([this](CHyprlandGlobalShortcutsManagerV1* pMgr) { PROTO::globalShortcuts->destroyResource(this); });\n\n    m_resource->setRegisterShortcut([this](CHyprlandGlobalShortcutsManagerV1* pMgr, uint32_t shortcut, const char* id, const char* app_id, const char* description,\n                                           const char* trigger_description) {\n        if UNLIKELY (PROTO::globalShortcuts->isTaken(id, app_id)) {\n            m_resource->error(HYPRLAND_GLOBAL_SHORTCUTS_MANAGER_V1_ERROR_ALREADY_TAKEN, \"Combination is taken\");\n            return;\n        }\n\n        const auto PSHORTCUT   = m_shortcuts.emplace_back(makeShared<SShortcut>(makeShared<CHyprlandGlobalShortcutV1>(m_resource->client(), m_resource->version(), shortcut)));\n        PSHORTCUT->id          = id;\n        PSHORTCUT->description = description;\n        PSHORTCUT->appid       = app_id;\n        PSHORTCUT->shortcut    = shortcut;\n\n        if UNLIKELY (!PSHORTCUT->resource->resource()) {\n            PSHORTCUT->resource->noMemory();\n            m_shortcuts.pop_back();\n            return;\n        }\n\n        PSHORTCUT->resource->setDestroy([this](CHyprlandGlobalShortcutV1* pMgr) { std::erase_if(m_shortcuts, [&](const auto& other) { return other->resource.get() == pMgr; }); });\n    });\n}\n\nbool CShortcutClient::good() {\n    return m_resource->resource();\n}\n\nCGlobalShortcutsProtocol::CGlobalShortcutsProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CGlobalShortcutsProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_clients.emplace_back(makeShared<CShortcutClient>(makeShared<CHyprlandGlobalShortcutsManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_clients.pop_back();\n        return;\n    }\n}\n\nvoid CGlobalShortcutsProtocol::destroyResource(CShortcutClient* client) {\n    std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; });\n}\n\nbool CGlobalShortcutsProtocol::isTaken(std::string appid, std::string trigger) {\n    for (auto const& c : m_clients) {\n        for (auto const& sh : c->m_shortcuts) {\n            if (sh->appid == appid && sh->id == trigger) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid CGlobalShortcutsProtocol::sendGlobalShortcutEvent(std::string appid, std::string trigger, bool pressed) {\n    for (auto const& c : m_clients) {\n        for (auto const& sh : c->m_shortcuts) {\n            if (sh->appid == appid && sh->id == trigger) {\n                const auto [sec, nsec] = Time::secNsec(Time::steadyNow());\n                uint32_t tvSecHi       = (sizeof(sec) > 4) ? sec >> 32 : 0;\n                uint32_t tvSecLo       = sec & 0xFFFFFFFF;\n                if (pressed)\n                    sh->resource->sendPressed(tvSecHi, tvSecLo, nsec);\n                else\n                    sh->resource->sendReleased(tvSecHi, tvSecLo, nsec);\n            }\n        }\n    }\n}\n\nstd::vector<SShortcut> CGlobalShortcutsProtocol::getAllShortcuts() {\n    std::vector<SShortcut> copy;\n    // calculate the total number of shortcuts, precomputing size is linear\n    // and potential reallocation is more costly then the added precompute overhead of looping\n    // and finding the total size.\n    size_t totalShortcuts = 0;\n    for (const auto& c : m_clients) {\n        totalShortcuts += c->m_shortcuts.size();\n    }\n\n    // reserve number of elements to avoid reallocations\n    copy.reserve(totalShortcuts);\n\n    for (const auto& c : m_clients) {\n        for (const auto& sh : c->m_shortcuts) {\n            copy.push_back(*sh);\n        }\n    }\n\n    return copy;\n}\n"
  },
  {
    "path": "src/protocols/GlobalShortcuts.hpp",
    "content": "#pragma once\n#include \"../defines.hpp\"\n#include \"hyprland-global-shortcuts-v1.hpp\"\n#include \"../protocols/WaylandProtocol.hpp\"\n#include <vector>\n\nstruct SShortcut {\n    SP<CHyprlandGlobalShortcutV1> resource;\n    std::string                   id, description, appid;\n    uint32_t                      shortcut = 0;\n};\n\nclass CShortcutClient {\n  public:\n    CShortcutClient(SP<CHyprlandGlobalShortcutsManagerV1> resource);\n\n    bool good();\n\n  private:\n    SP<CHyprlandGlobalShortcutsManagerV1> m_resource;\n    std::vector<SP<SShortcut>>            m_shortcuts;\n\n    friend class CGlobalShortcutsProtocol;\n};\n\nclass CGlobalShortcutsProtocol : IWaylandProtocol {\n  public:\n    CGlobalShortcutsProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    void                   bindManager(wl_client* client, void* data, uint32_t version, uint32_t id);\n    void                   destroyResource(CShortcutClient* client);\n\n    void                   sendGlobalShortcutEvent(std::string appid, std::string trigger, bool pressed);\n    bool                   isTaken(std::string id, std::string app_id);\n    std::vector<SShortcut> getAllShortcuts();\n\n  private:\n    std::vector<SP<CShortcutClient>> m_clients;\n};\n\nnamespace PROTO {\n    inline UP<CGlobalShortcutsProtocol> globalShortcuts;\n};\n"
  },
  {
    "path": "src/protocols/HyprlandSurface.cpp",
    "content": "#include \"HyprlandSurface.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"hyprland-surface-v1.hpp\"\n#include <hyprutils/math/Region.hpp>\n#include <wayland-server.h>\n\nCHyprlandSurface::CHyprlandSurface(SP<CHyprlandSurfaceV1> resource, SP<CWLSurfaceResource> surface) : m_surface(surface) {\n    setResource(std::move(resource));\n}\n\nbool CHyprlandSurface::good() const {\n    return m_resource->resource();\n}\n\nvoid CHyprlandSurface::setResource(SP<CHyprlandSurfaceV1> resource) {\n    m_resource = std::move(resource);\n\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CHyprlandSurfaceV1* resource) { destroy(); });\n    m_resource->setOnDestroy([this](CHyprlandSurfaceV1* resource) { destroy(); });\n\n    m_resource->setSetOpacity([this](CHyprlandSurfaceV1* resource, uint32_t opacity) {\n        if UNLIKELY (!m_surface) {\n            m_resource->error(HYPRLAND_SURFACE_V1_ERROR_NO_SURFACE, \"set_opacity called for destroyed wl_surface\");\n            return;\n        }\n\n        auto fOpacity = wl_fixed_to_double(opacity);\n        if UNLIKELY (fOpacity < 0.0 || fOpacity > 1.0) {\n            m_resource->error(HYPRLAND_SURFACE_V1_ERROR_OUT_OF_RANGE, \"set_opacity called with an opacity value larger than 1.0 or smaller than 0.0.\");\n            return;\n        }\n\n        m_opacity = fOpacity;\n    });\n\n    m_resource->setSetVisibleRegion([this](CHyprlandSurfaceV1* resource, wl_resource* region) {\n        if (!region) {\n            if (!m_visibleRegion.empty())\n                m_visibleRegionChanged = true;\n\n            m_visibleRegion.clear();\n            return;\n        }\n\n        m_visibleRegionChanged = true;\n        m_visibleRegion        = CWLRegionResource::fromResource(region)->m_region;\n    });\n\n    m_listeners.surfaceCommitted = m_surface->m_events.commit.listen([this] {\n        auto surface = Desktop::View::CWLSurface::fromResource(m_surface.lock());\n\n        if (surface && (surface->m_overallOpacity != m_opacity || m_visibleRegionChanged)) {\n            surface->m_overallOpacity = m_opacity;\n            surface->m_visibleRegion  = m_visibleRegion;\n            auto box                  = surface->getSurfaceBoxGlobal();\n\n            if (box.has_value())\n                g_pHyprRenderer->damageBox(*box);\n\n            if (!m_resource)\n                PROTO::hyprlandSurface->destroySurface(this);\n        }\n    });\n\n    m_listeners.surfaceDestroyed = m_surface->m_events.destroy.listen([this] {\n        if (!m_resource)\n            PROTO::hyprlandSurface->destroySurface(this);\n    });\n}\n\nvoid CHyprlandSurface::destroy() {\n    m_resource.reset();\n    m_opacity = 1.F;\n\n    if (!m_visibleRegion.empty())\n        m_visibleRegionChanged = true;\n\n    m_visibleRegion.clear();\n\n    if (!m_surface)\n        PROTO::hyprlandSurface->destroySurface(this);\n}\n\nCHyprlandSurfaceProtocol::CHyprlandSurfaceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CHyprlandSurfaceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    auto manager = m_managers.emplace_back(makeUnique<CHyprlandSurfaceManagerV1>(client, ver, id)).get();\n    manager->setOnDestroy([this](CHyprlandSurfaceManagerV1* manager) { destroyManager(manager); });\n\n    manager->setDestroy([this](CHyprlandSurfaceManagerV1* manager) { destroyManager(manager); });\n    manager->setGetHyprlandSurface(\n        [this](CHyprlandSurfaceManagerV1* manager, uint32_t id, wl_resource* surface) { getSurface(manager, id, CWLSurfaceResource::fromResource(surface)); });\n}\n\nvoid CHyprlandSurfaceProtocol::destroyManager(CHyprlandSurfaceManagerV1* manager) {\n    std::erase_if(m_managers, [&](const auto& p) { return p.get() == manager; });\n}\n\nvoid CHyprlandSurfaceProtocol::destroySurface(CHyprlandSurface* surface) {\n    std::erase_if(m_surfaces, [&](const auto& entry) { return entry.second.get() == surface; });\n}\n\nvoid CHyprlandSurfaceProtocol::getSurface(CHyprlandSurfaceManagerV1* manager, uint32_t id, SP<CWLSurfaceResource> surface) {\n    CHyprlandSurface* hyprlandSurface = nullptr;\n    auto              iter            = std::ranges::find_if(m_surfaces, [&](const auto& entry) { return entry.second->m_surface == surface; });\n\n    if (iter != m_surfaces.end()) {\n        if (iter->second->m_resource) {\n            LOGM(Log::ERR, \"HyprlandSurface already present for surface {:x}\", (uintptr_t)surface.get());\n            manager->error(HYPRLAND_SURFACE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, \"HyprlandSurface already present\");\n            return;\n        } else {\n            iter->second->setResource(makeShared<CHyprlandSurfaceV1>(manager->client(), manager->version(), id));\n            hyprlandSurface = iter->second.get();\n        }\n    } else {\n        hyprlandSurface =\n            m_surfaces.emplace(surface, makeUnique<CHyprlandSurface>(makeShared<CHyprlandSurfaceV1>(manager->client(), manager->version(), id), surface)).first->second.get();\n    }\n\n    if UNLIKELY (!hyprlandSurface->good()) {\n        manager->noMemory();\n        m_surfaces.erase(surface);\n    }\n}\n"
  },
  {
    "path": "src/protocols/HyprlandSurface.hpp",
    "content": "#pragma once\n\n#include <hyprutils/math/Region.hpp>\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"hyprland-surface-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\nclass CHyprlandSurfaceProtocol;\n\nclass CHyprlandSurface {\n  public:\n    CHyprlandSurface(SP<CHyprlandSurfaceV1> resource, SP<CWLSurfaceResource> surface);\n\n    bool good() const;\n    void setResource(SP<CHyprlandSurfaceV1> resource);\n\n  private:\n    SP<CHyprlandSurfaceV1> m_resource;\n    WP<CWLSurfaceResource> m_surface;\n    float                  m_opacity              = 1.0;\n    bool                   m_visibleRegionChanged = false;\n    CRegion                m_visibleRegion;\n\n    void                   destroy();\n\n    struct {\n        CHyprSignalListener surfaceCommitted;\n        CHyprSignalListener surfaceDestroyed;\n    } m_listeners;\n\n    friend class CHyprlandSurfaceProtocol;\n};\n\nclass CHyprlandSurfaceProtocol : public IWaylandProtocol {\n  public:\n    CHyprlandSurfaceProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void                                                             destroyManager(CHyprlandSurfaceManagerV1* res);\n    void                                                             destroySurface(CHyprlandSurface* surface);\n    void                                                             getSurface(CHyprlandSurfaceManagerV1* manager, uint32_t id, SP<CWLSurfaceResource> surface);\n\n    std::vector<UP<CHyprlandSurfaceManagerV1>>                       m_managers;\n    std::unordered_map<WP<CWLSurfaceResource>, UP<CHyprlandSurface>> m_surfaces;\n\n    friend class CHyprlandSurface;\n};\n\nnamespace PROTO {\n    inline UP<CHyprlandSurfaceProtocol> hyprlandSurface;\n}\n"
  },
  {
    "path": "src/protocols/IdleInhibit.cpp",
    "content": "#include \"IdleInhibit.hpp\"\n#include \"core/Compositor.hpp\"\n\nCIdleInhibitor::CIdleInhibitor(SP<CIdleInhibitorResource> resource_, SP<CWLSurfaceResource> surf_) : m_resource(resource_), m_surface(surf_) {\n    ;\n}\n\nCIdleInhibitorResource::CIdleInhibitorResource(SP<CZwpIdleInhibitorV1> resource_, SP<CWLSurfaceResource> surface_) : m_resource(resource_), m_surface(surface_) {\n    m_listeners.destroySurface = m_surface->m_events.destroy.listen([this] {\n        m_surface.reset();\n        m_listeners.destroySurface.reset();\n        m_destroySent = true;\n        m_events.destroy.emit();\n    });\n\n    m_resource->setOnDestroy([this](CZwpIdleInhibitorV1* p) { PROTO::idleInhibit->removeInhibitor(this); });\n    m_resource->setDestroy([this](CZwpIdleInhibitorV1* p) { PROTO::idleInhibit->removeInhibitor(this); });\n}\n\nCIdleInhibitorResource::~CIdleInhibitorResource() {\n    if (!m_destroySent)\n        m_events.destroy.emit();\n}\n\nCIdleInhibitProtocol::CIdleInhibitProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CIdleInhibitProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [res](const auto& other) { return other->resource() == res; });\n}\n\nvoid CIdleInhibitProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpIdleInhibitManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpIdleInhibitManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpIdleInhibitManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setCreateInhibitor(\n        [this](CZwpIdleInhibitManagerV1* pMgr, uint32_t id, wl_resource* surface) { this->onCreateInhibitor(pMgr, id, CWLSurfaceResource::fromResource(surface)); });\n}\n\nvoid CIdleInhibitProtocol::removeInhibitor(CIdleInhibitorResource* resource) {\n    std::erase_if(m_inhibitors, [resource](const auto& el) { return el.get() == resource; });\n}\n\nvoid CIdleInhibitProtocol::onCreateInhibitor(CZwpIdleInhibitManagerV1* pMgr, uint32_t id, SP<CWLSurfaceResource> surface) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_inhibitors.emplace_back(makeShared<CIdleInhibitorResource>(makeShared<CZwpIdleInhibitorV1>(CLIENT, pMgr->version(), id), surface));\n\n    RESOURCE->m_inhibitor = makeShared<CIdleInhibitor>(RESOURCE, surface);\n    m_events.newIdleInhibitor.emit(RESOURCE->m_inhibitor);\n}\n"
  },
  {
    "path": "src/protocols/IdleInhibit.hpp",
    "content": "#pragma once\n\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"idle-inhibit-unstable-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CIdleInhibitorResource;\nclass CWLSurfaceResource;\n\nclass CIdleInhibitor {\n  public:\n    CIdleInhibitor(SP<CIdleInhibitorResource> resource_, SP<CWLSurfaceResource> surf_);\n\n    struct {\n        CHyprSignalListener destroy;\n    } m_listeners;\n\n    WP<CIdleInhibitorResource> m_resource;\n    WP<CWLSurfaceResource>     m_surface;\n};\n\nclass CIdleInhibitorResource {\n  public:\n    CIdleInhibitorResource(SP<CZwpIdleInhibitorV1> resource_, SP<CWLSurfaceResource> surface_);\n    ~CIdleInhibitorResource();\n\n    SP<CIdleInhibitor> m_inhibitor;\n\n    struct {\n        CSignalT<> destroy;\n    } m_events;\n\n  private:\n    SP<CZwpIdleInhibitorV1> m_resource;\n    WP<CWLSurfaceResource>  m_surface;\n    bool                    m_destroySent = false;\n\n    struct {\n        CHyprSignalListener destroySurface;\n    } m_listeners;\n};\n\nclass CIdleInhibitProtocol : public IWaylandProtocol {\n  public:\n    CIdleInhibitProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<SP<CIdleInhibitor>> newIdleInhibitor;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void onCreateInhibitor(CZwpIdleInhibitManagerV1* pMgr, uint32_t id, SP<CWLSurfaceResource> surface);\n\n    void removeInhibitor(CIdleInhibitorResource*);\n\n    //\n    std::vector<UP<CZwpIdleInhibitManagerV1>> m_managers;\n    std::vector<SP<CIdleInhibitorResource>>   m_inhibitors;\n\n    friend class CIdleInhibitorResource;\n};\n\nnamespace PROTO {\n    inline UP<CIdleInhibitProtocol> idleInhibit;\n}\n"
  },
  {
    "path": "src/protocols/IdleNotify.cpp",
    "content": "#include \"IdleNotify.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n\nstatic int onTimer(SP<CEventLoopTimer> self, void* data) {\n\n    const auto NOTIF = sc<CExtIdleNotification*>(data);\n\n    NOTIF->onTimerFired();\n\n    return 0;\n}\n\nCExtIdleNotification::CExtIdleNotification(SP<CExtIdleNotificationV1> resource_, uint32_t timeoutMs_, bool obeyInhibitors_) :\n    m_resource(resource_), m_timeoutMs(timeoutMs_), m_obeyInhibitors(obeyInhibitors_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setDestroy([this](CExtIdleNotificationV1* r) { PROTO::idle->destroyNotification(this); });\n    m_resource->setOnDestroy([this](CExtIdleNotificationV1* r) { PROTO::idle->destroyNotification(this); });\n\n    m_timer = makeShared<CEventLoopTimer>(std::nullopt, onTimer, this);\n    g_pEventLoopManager->addTimer(m_timer);\n\n    update();\n\n    LOGM(Log::DEBUG, \"Registered idle-notification for {}ms\", timeoutMs_);\n}\n\nCExtIdleNotification::~CExtIdleNotification() {\n    g_pEventLoopManager->removeTimer(m_timer);\n    m_timer.reset();\n}\n\nbool CExtIdleNotification::good() {\n    return m_resource->resource();\n}\n\nvoid CExtIdleNotification::update(uint32_t elapsedMs) {\n    m_timer->updateTimeout(std::nullopt);\n\n    if (elapsedMs == 0 && PROTO::idle->isInhibited && m_obeyInhibitors) {\n        reset();\n        return;\n    }\n\n    if (m_timeoutMs > elapsedMs) {\n        reset();\n        m_timer->updateTimeout(std::chrono::milliseconds(m_timeoutMs - elapsedMs));\n    } else\n        onTimerFired();\n}\n\nvoid CExtIdleNotification::update() {\n    update(0);\n}\n\nvoid CExtIdleNotification::onTimerFired() {\n    if (m_idled)\n        return;\n\n    m_resource->sendIdled();\n    m_idled = true;\n}\n\nvoid CExtIdleNotification::reset() {\n    if (!m_idled)\n        return;\n\n    m_resource->sendResumed();\n    m_idled = false;\n}\n\nbool CExtIdleNotification::inhibitorsAreObeyed() const {\n    return m_obeyInhibitors;\n}\n\nCIdleNotifyProtocol::CIdleNotifyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CIdleNotifyProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CExtIdleNotifierV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CExtIdleNotifierV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CExtIdleNotifierV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetIdleNotification(\n        [this](CExtIdleNotifierV1* pMgr, uint32_t id, uint32_t timeout, wl_resource* seat) { this->onGetNotification(pMgr, id, timeout, seat, true); });\n    RESOURCE->setGetInputIdleNotification(\n        [this](CExtIdleNotifierV1* pMgr, uint32_t id, uint32_t timeout, wl_resource* seat) { this->onGetNotification(pMgr, id, timeout, seat, false); });\n}\n\nvoid CIdleNotifyProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CIdleNotifyProtocol::destroyNotification(CExtIdleNotification* notif) {\n    std::erase_if(m_notifications, [&](const auto& other) { return other.get() == notif; });\n}\n\nvoid CIdleNotifyProtocol::onGetNotification(CExtIdleNotifierV1* pMgr, uint32_t id, uint32_t timeout, wl_resource* seat, bool obeyInhibitors) {\n    const auto CLIENT = pMgr->client();\n    const auto RESOURCE =\n        m_notifications.emplace_back(makeShared<CExtIdleNotification>(makeShared<CExtIdleNotificationV1>(CLIENT, pMgr->version(), id), timeout, obeyInhibitors)).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_notifications.pop_back();\n        return;\n    }\n}\n\nvoid CIdleNotifyProtocol::onActivity() {\n    for (auto const& n : m_notifications) {\n        n->update();\n    }\n}\n\nvoid CIdleNotifyProtocol::setInhibit(bool inhibited) {\n    isInhibited = inhibited;\n    for (auto const& n : m_notifications) {\n        if (n->inhibitorsAreObeyed())\n            n->update();\n    }\n}\n\nvoid CIdleNotifyProtocol::setTimers(uint32_t elapsedMs) {\n    for (auto const& n : m_notifications) {\n        n->update(elapsedMs);\n    }\n}\n"
  },
  {
    "path": "src/protocols/IdleNotify.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"ext-idle-notify-v1.hpp\"\n\nclass CEventLoopTimer;\n\nclass CExtIdleNotification {\n  public:\n    CExtIdleNotification(SP<CExtIdleNotificationV1> resource_, uint32_t timeoutMs, bool obeyInhibitors);\n    ~CExtIdleNotification();\n\n    bool good();\n    void onTimerFired();\n\n    bool inhibitorsAreObeyed() const;\n\n  private:\n    SP<CExtIdleNotificationV1> m_resource;\n    uint32_t                   m_timeoutMs = 0;\n    SP<CEventLoopTimer>        m_timer;\n\n    bool                       m_idled          = false;\n    bool                       m_obeyInhibitors = false;\n\n    void                       reset();\n    void                       update();\n    void                       update(uint32_t elapsedMs);\n\n    friend class CIdleNotifyProtocol;\n};\n\nclass CIdleNotifyProtocol : public IWaylandProtocol {\n  public:\n    CIdleNotifyProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         onActivity();\n    void         setInhibit(bool inhibited);\n    void         setTimers(uint32_t elapsedMs);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyNotification(CExtIdleNotification* notif);\n    void onGetNotification(CExtIdleNotifierV1* pMgr, uint32_t id, uint32_t timeout, wl_resource* seat, bool obeyInhibitors);\n\n    bool isInhibited = false;\n\n    //\n    std::vector<UP<CExtIdleNotifierV1>>   m_managers;\n    std::vector<SP<CExtIdleNotification>> m_notifications;\n\n    friend class CExtIdleNotification;\n};\n\nnamespace PROTO {\n    inline UP<CIdleNotifyProtocol> idle;\n};\n"
  },
  {
    "path": "src/protocols/ImageCaptureSource.cpp",
    "content": "#include \"ImageCaptureSource.hpp\"\n#include \"core/Output.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"ForeignToplevel.hpp\"\n\nCImageCaptureSource::CImageCaptureSource(SP<CExtImageCaptureSourceV1> resource, PHLMONITOR pMonitor) : m_resource(resource), m_monitor(pMonitor) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n    m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); });\n}\n\nCImageCaptureSource::CImageCaptureSource(SP<CExtImageCaptureSourceV1> resource, PHLWINDOW pWindow) : m_resource(resource), m_window(pWindow) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n    m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); });\n}\n\nbool CImageCaptureSource::good() {\n    return m_resource && m_resource->resource();\n}\n\nstd::string CImageCaptureSource::getName() {\n    if (!m_monitor.expired())\n        return m_monitor->m_name;\n    if (!m_window.expired())\n        return m_window->m_title;\n\n    return \"error\";\n}\n\nstd::string CImageCaptureSource::getTypeName() {\n    if (!m_monitor.expired())\n        return \"monitor\";\n    if (!m_window.expired())\n        return \"window\";\n\n    return \"error\";\n}\n\nCBox CImageCaptureSource::logicalBox() {\n    if (!m_monitor.expired())\n        return m_monitor->logicalBox();\n    if (!m_window.expired())\n        return m_window->getFullWindowBoundingBox();\n    return CBox();\n}\n\nCOutputImageCaptureSourceProtocol::COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid COutputImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = PROTO::imageCaptureSource->m_outputManagers.emplace_back(makeShared<CExtOutputImageCaptureSourceManagerV1>(client, ver, id));\n\n    if UNLIKELY (!RESOURCE->resource()) {\n        wl_client_post_no_memory(client);\n        PROTO::imageCaptureSource->m_outputManagers.pop_back();\n        return;\n    }\n\n    RESOURCE->setDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); });\n    RESOURCE->setOnDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); });\n    RESOURCE->setCreateSource([](CExtOutputImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* output) {\n        PHLMONITOR pMonitor = CWLOutputResource::fromResource(output)->m_monitor.lock();\n        if (!pMonitor) {\n            LOGM(Log::ERR, \"Client tried to create source from invalid output resource\");\n            pMgr->error(-1, \"invalid output resource\");\n            return;\n        }\n\n        auto PSOURCE =\n            PROTO::imageCaptureSource->m_sources.emplace_back(makeShared<CImageCaptureSource>(makeShared<CExtImageCaptureSourceV1>(pMgr->client(), pMgr->version(), id), pMonitor));\n        PSOURCE->m_self = PSOURCE;\n\n        LOGM(Log::INFO, \"New capture source for monitor: {}\", pMonitor->m_name);\n    });\n}\n\nCToplevelImageCaptureSourceProtocol::CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CToplevelImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = PROTO::imageCaptureSource->m_toplevelManagers.emplace_back(makeShared<CExtForeignToplevelImageCaptureSourceManagerV1>(client, ver, id));\n\n    if UNLIKELY (!RESOURCE->resource()) {\n        RESOURCE->noMemory();\n        PROTO::imageCaptureSource->m_toplevelManagers.pop_back();\n        return;\n    }\n\n    RESOURCE->setDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); });\n    RESOURCE->setOnDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); });\n    RESOURCE->setCreateSource([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* handle) {\n        PHLWINDOW pWindow = PROTO::foreignToplevel->windowFromHandleResource(handle);\n        if (!pWindow) {\n            LOGM(Log::ERR, \"Client tried to create source from invalid foreign toplevel handle resource\");\n            pMgr->error(-1, \"invalid foreign toplevel resource\");\n            return;\n        }\n\n        auto PSOURCE =\n            PROTO::imageCaptureSource->m_sources.emplace_back(makeShared<CImageCaptureSource>(makeShared<CExtImageCaptureSourceV1>(pMgr->client(), pMgr->version(), id), pWindow));\n        PSOURCE->m_self = PSOURCE;\n\n        LOGM(Log::INFO, \"New capture source for foreign toplevel: {}\", pWindow->m_title);\n    });\n}\n\nCImageCaptureSourceProtocol::CImageCaptureSourceProtocol() {\n    m_output   = makeUnique<COutputImageCaptureSourceProtocol>(&ext_output_image_capture_source_manager_v1_interface, 1, \"OutputImageCaptureSource\");\n    m_toplevel = makeUnique<CToplevelImageCaptureSourceProtocol>(&ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1, \"ForeignToplevelImageCaptureSource\");\n}\n\nSP<CImageCaptureSource> CImageCaptureSourceProtocol::sourceFromResource(wl_resource* res) {\n    auto data = sc<CImageCaptureSource*>(sc<CExtImageCaptureSourceV1*>(wl_resource_get_user_data(res))->data());\n    return data && data->m_self ? data->m_self.lock() : nullptr;\n}\n\nvoid CImageCaptureSourceProtocol::destroyResource(CExtOutputImageCaptureSourceManagerV1* resource) {\n    std::erase_if(m_outputManagers, [&](const auto& other) { return other.get() == resource; });\n}\nvoid CImageCaptureSourceProtocol::destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource) {\n    std::erase_if(m_toplevelManagers, [&](const auto& other) { return other.get() == resource; });\n}\nvoid CImageCaptureSourceProtocol::destroyResource(CImageCaptureSource* resource) {\n    std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/ImageCaptureSource.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"../defines.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"WaylandProtocol.hpp\"\n#include \"ext-image-capture-source-v1.hpp\"\n\nclass CImageCopyCaptureSession;\n\nclass CImageCaptureSource {\n  public:\n    CImageCaptureSource(SP<CExtImageCaptureSourceV1> resource, PHLMONITOR pMonitor);\n    CImageCaptureSource(SP<CExtImageCaptureSourceV1> resource, PHLWINDOW pWindow);\n\n    bool                    good();\n    std::string             getName();\n    std::string             getTypeName();\n    CBox                    logicalBox();\n\n    WP<CImageCaptureSource> m_self;\n\n  private:\n    SP<CExtImageCaptureSourceV1> m_resource;\n\n    PHLMONITORREF                m_monitor;\n    PHLWINDOWREF                 m_window;\n\n    friend class CImageCopyCaptureSession;\n    friend class CImageCopyCaptureCursorSession;\n};\n\nclass COutputImageCaptureSourceProtocol : public IWaylandProtocol {\n  public:\n    COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n};\n\nclass CToplevelImageCaptureSourceProtocol : public IWaylandProtocol {\n  public:\n    CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n};\n\nclass CImageCaptureSourceProtocol {\n  public:\n    CImageCaptureSourceProtocol();\n\n    SP<CImageCaptureSource> sourceFromResource(wl_resource* resource);\n\n    void                    destroyResource(CExtOutputImageCaptureSourceManagerV1* resource);\n    void                    destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource);\n    void                    destroyResource(CImageCaptureSource* resource);\n\n  private:\n    UP<COutputImageCaptureSourceProtocol>                           m_output;\n    UP<CToplevelImageCaptureSourceProtocol>                         m_toplevel;\n\n    std::vector<SP<CExtOutputImageCaptureSourceManagerV1>>          m_outputManagers;\n    std::vector<SP<CExtForeignToplevelImageCaptureSourceManagerV1>> m_toplevelManagers;\n\n    std::vector<SP<CImageCaptureSource>>                            m_sources;\n\n    friend class COutputImageCaptureSourceProtocol;\n    friend class CToplevelImageCaptureSourceProtocol;\n};\n\nnamespace PROTO {\n    inline UP<CImageCaptureSourceProtocol> imageCaptureSource;\n};\n"
  },
  {
    "path": "src/protocols/ImageCopyCapture.cpp",
    "content": "#include \"ImageCopyCapture.hpp\"\n#include \"../managers/screenshare/ScreenshareManager.hpp\"\n#include \"../managers/permissions/DynamicPermissionManager.hpp\"\n#include \"../managers/PointerManager.hpp\"\n#include \"./core/Seat.hpp\"\n#include \"LinuxDMABUF.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"../render/OpenGL.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"render/Renderer.hpp\"\n#include <cstring>\n\nusing namespace Screenshare;\n\nCImageCopyCaptureSession::CImageCopyCaptureSession(SP<CExtImageCopyCaptureSessionV1> resource, SP<CImageCaptureSource> source, extImageCopyCaptureManagerV1Options options) :\n    m_resource(resource), m_source(source), m_paintCursor(options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });\n\n    m_resource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) {\n        if (!m_frame.expired()) {\n            LOGM(Log::ERR, \"Duplicate frame in session for source: \\\"{}\\\"\", m_source->getName());\n            m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, \"duplicate frame\");\n            return;\n        }\n\n        auto PFRAME = PROTO::imageCopyCapture->m_frames.emplace_back(\n            makeShared<CImageCopyCaptureFrame>(makeShared<CExtImageCopyCaptureFrameV1>(pMgr->client(), pMgr->version(), id), m_self));\n\n        m_frame = PFRAME;\n    });\n\n    if (m_source->m_monitor)\n        m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_monitor.lock());\n    else\n        m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_window.lock());\n\n    if UNLIKELY (!m_session) {\n        m_resource->sendStopped();\n        return;\n    }\n\n    sendConstraints();\n\n    m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); });\n    m_listeners.stopped            = m_session->m_events.stopped.listen([this]() { m_resource->sendStopped(); });\n}\n\nCImageCopyCaptureSession::~CImageCopyCaptureSession() {\n    if (m_session)\n        m_session->stop();\n    if (m_resource->resource())\n        m_resource->sendStopped();\n}\n\nbool CImageCopyCaptureSession::good() {\n    return m_resource && m_resource->resource();\n}\n\nvoid CImageCopyCaptureSession::sendConstraints() {\n    auto formats = m_session->allowedFormats();\n\n    if UNLIKELY (formats.empty()) {\n        m_session->stop();\n        return;\n    }\n\n    for (DRMFormat format : formats) {\n        m_resource->sendShmFormat(NFormatUtils::drmToShm(format));\n\n        auto     modifiers = g_pHyprRenderer->getDRMFormatModifiers(format);\n\n        wl_array modsArr;\n        wl_array_init(&modsArr);\n        if (!modifiers.empty()) {\n            wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t));\n            memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t));\n        }\n        m_resource->sendDmabufFormat(format, &modsArr);\n        wl_array_release(&modsArr);\n    }\n\n    dev_t           device    = PROTO::linuxDma->getMainDevice();\n    struct wl_array deviceArr = {\n        .size = sizeof(device),\n        .data = sc<void*>(&device),\n    };\n    m_resource->sendDmabufDevice(&deviceArr);\n\n    m_bufferSize = m_session->bufferSize();\n    m_resource->sendBufferSize(m_bufferSize.x, m_bufferSize.y);\n\n    m_resource->sendDone();\n}\n\nCImageCopyCaptureCursorSession::CImageCopyCaptureCursorSession(SP<CExtImageCopyCaptureCursorSessionV1> resource, SP<CImageCaptureSource> source, SP<CWLPointerResource> pointer) :\n    m_resource(resource), m_source(source), m_pointer(pointer) {\n    if UNLIKELY (!good())\n        return;\n\n    if (!m_source || (!m_source->m_monitor && !m_source->m_window))\n        return;\n\n    const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock();\n\n    // TODO: add listeners for source being destroyed\n\n    sendCursorEvents();\n    m_listeners.commit = PMONITOR->m_events.commit.listen([this, PMONITOR]() { sendCursorEvents(); });\n\n    m_resource->setDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });\n\n    m_resource->setGetCaptureSession([this](CExtImageCopyCaptureCursorSessionV1* pMgr, uint32_t id) {\n        if (m_session || m_sessionResource) {\n            LOGM(Log::ERR, \"Duplicate cursor copy capture session for source: \\\"{}\\\"\", m_source->getName());\n            m_resource->error(EXT_IMAGE_COPY_CAPTURE_CURSOR_SESSION_V1_ERROR_DUPLICATE_SESSION, \"duplicate session\");\n            return;\n        }\n\n        m_sessionResource = makeShared<CExtImageCopyCaptureSessionV1>(pMgr->client(), pMgr->version(), id);\n\n        m_sessionResource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); });\n        m_sessionResource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); });\n\n        m_sessionResource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) {\n            if UNLIKELY (!m_session || !m_sessionResource)\n                return;\n\n            if (m_frameResource) {\n                LOGM(Log::ERR, \"Duplicate frame in session for source: \\\"{}\\\"\", m_source->getName());\n                m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, \"duplicate frame\");\n                return;\n            }\n\n            createFrame(makeShared<CExtImageCopyCaptureFrameV1>(pMgr->client(), pMgr->version(), id));\n        });\n\n        m_session = Screenshare::mgr()->newCursorSession(pMgr->client(), m_pointer);\n        if UNLIKELY (!m_session) {\n            m_sessionResource->sendStopped();\n            return;\n        }\n\n        sendConstraints();\n\n        m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); });\n        m_listeners.stopped            = m_session->m_events.stopped.listen([this]() { destroyCaptureSession(); });\n    });\n}\n\nCImageCopyCaptureCursorSession::~CImageCopyCaptureCursorSession() {\n    destroyCaptureSession();\n}\n\nbool CImageCopyCaptureCursorSession::good() {\n    return m_resource && m_resource->resource();\n}\n\nvoid CImageCopyCaptureCursorSession::destroyCaptureSession() {\n    m_listeners.constraintsChanged.reset();\n    m_listeners.stopped.reset();\n\n    if (m_frameResource && m_frameResource->resource())\n        m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED);\n    m_frameResource.reset();\n\n    m_sessionResource.reset();\n    m_session.reset();\n}\n\nvoid CImageCopyCaptureCursorSession::createFrame(SP<CExtImageCopyCaptureFrameV1> resource) {\n    m_frameResource = resource;\n    m_captured      = false;\n    m_buffer.reset();\n\n    m_frameResource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); });\n    m_frameResource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); });\n\n    m_frameResource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) {\n        if UNLIKELY (!m_frameResource || !m_frameResource->resource())\n            return;\n\n        if (m_captured) {\n            LOGM(Log::ERR, \"Frame already captured in attach_buffer, {:x}\", (uintptr_t)this);\n            m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, \"already captured\");\n            m_frameResource.reset();\n            return;\n        }\n\n        auto PBUFFERRES = CWLBufferResource::fromResource(buf);\n        if (!PBUFFERRES || !PBUFFERRES->m_buffer) {\n            LOGM(Log::ERR, \"Invalid buffer in attach_buffer {:x}\", (uintptr_t)this);\n            m_frameResource->error(-1, \"invalid buffer\");\n            m_frameResource.reset();\n            return;\n        }\n\n        m_buffer = PBUFFERRES->m_buffer.lock();\n    });\n\n    m_frameResource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) {\n        if UNLIKELY (!m_frameResource || !m_frameResource->resource())\n            return;\n\n        if (m_captured) {\n            LOGM(Log::ERR, \"Frame already captured in damage_buffer, {:x}\", (uintptr_t)this);\n            m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, \"already captured\");\n            m_frameResource.reset();\n            return;\n        }\n\n        if (x < 0 || y < 0 || w <= 0 || h <= 0) {\n            m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, \"invalid buffer damage\");\n            m_frameResource.reset();\n            return;\n        }\n\n        // we don't really need to keep track of damage for cursor frames because we will just copy the whole thing\n    });\n\n    m_frameResource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) {\n        if UNLIKELY (!m_frameResource || !m_frameResource->resource())\n            return;\n\n        if (m_captured) {\n            LOGM(Log::ERR, \"Frame already captured in capture, {:x}\", (uintptr_t)this);\n            m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, \"already captured\");\n            m_frameResource.reset();\n            return;\n        }\n\n        const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock();\n\n        auto       sourceBoxCallback = [this]() { return m_source ? m_source->logicalBox() : CBox(); };\n        auto       error             = m_session->share(PMONITOR, m_buffer, sourceBoxCallback, [this](eScreenshareResult result) {\n            switch (result) {\n                case RESULT_COPIED: m_frameResource->sendReady(); break;\n                case RESULT_NOT_COPIED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break;\n                case RESULT_TIMESTAMP:\n                    auto [sec, nsec] = Time::secNsec(Time::steadyNow());\n                    uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0;\n                    uint32_t tvSecLo = sec & 0xFFFFFFFF;\n                    m_frameResource->sendPresentationTime(tvSecHi, tvSecLo, nsec);\n                    break;\n            }\n        });\n\n        if (!m_frameResource)\n            return;\n\n        switch (error) {\n            case ERROR_NONE: m_captured = true; break;\n            case ERROR_NO_BUFFER:\n                m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, \"no buffer attached\");\n                m_frameResource.reset();\n                break;\n            case ERROR_BUFFER_SIZE:\n            case ERROR_BUFFER_FORMAT: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break;\n            case ERROR_STOPPED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break;\n            case ERROR_UNKNOWN: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break;\n        }\n    });\n\n    // we should always copy over the entire cursor image, it doesn't cost much\n    m_frameResource->sendDamage(0, 0, m_bufferSize.x, m_bufferSize.y);\n\n    // the cursor is never transformed... probably?\n    m_frameResource->sendTransform(WL_OUTPUT_TRANSFORM_NORMAL);\n}\n\nvoid CImageCopyCaptureCursorSession::sendConstraints() {\n    if UNLIKELY (!m_session || !m_sessionResource)\n        return;\n\n    auto format = m_session->format();\n    if UNLIKELY (format == DRM_FORMAT_INVALID) {\n        m_session->stop();\n        return;\n    }\n\n    m_sessionResource->sendShmFormat(NFormatUtils::drmToShm(format));\n\n    auto     modifiers = g_pHyprRenderer->getDRMFormatModifiers(format);\n\n    wl_array modsArr;\n    wl_array_init(&modsArr);\n    if (!modifiers.empty()) {\n        wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t));\n        memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t));\n    }\n    m_sessionResource->sendDmabufFormat(format, &modsArr);\n    wl_array_release(&modsArr);\n\n    dev_t           device    = PROTO::linuxDma->getMainDevice();\n    struct wl_array deviceArr = {\n        .size = sizeof(device),\n        .data = sc<void*>(&device),\n    };\n    m_sessionResource->sendDmabufDevice(&deviceArr);\n\n    m_bufferSize = m_session->bufferSize();\n    m_sessionResource->sendBufferSize(m_bufferSize.x, m_bufferSize.y);\n\n    m_sessionResource->sendDone();\n}\n\nvoid CImageCopyCaptureCursorSession::sendCursorEvents() {\n    const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_CURSOR_POS);\n    if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW)\n        return;\n\n    const auto PMONITOR  = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock();\n    CBox       sourceBox = m_source->logicalBox();\n    bool       overlaps  = g_pPointerManager->getCursorBoxGlobal().overlaps(sourceBox);\n\n    if (m_entered && !overlaps) {\n        m_entered = false;\n        m_resource->sendLeave();\n        return;\n    } else if (!m_entered && overlaps) {\n        m_entered = true;\n        m_resource->sendEnter();\n    }\n\n    if (!overlaps)\n        return;\n\n    Vector2D pos = g_pPointerManager->position() - sourceBox.pos();\n    if (pos != m_pos) {\n        m_pos = pos;\n        m_resource->sendPosition(m_pos.x, m_pos.y);\n    }\n\n    Vector2D hotspot = g_pPointerManager->hotspot();\n    if (hotspot != m_hotspot) {\n        m_hotspot = hotspot;\n        m_resource->sendHotspot(m_hotspot.x, m_hotspot.y);\n    }\n}\n\nCImageCopyCaptureFrame::CImageCopyCaptureFrame(SP<CExtImageCopyCaptureFrameV1> resource, WP<CImageCopyCaptureSession> session) : m_resource(resource), m_session(session) {\n    if UNLIKELY (!good())\n        return;\n\n    if (m_session->m_bufferSize != m_session->m_session->bufferSize()) {\n        m_session->sendConstraints();\n        m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN);\n        return;\n    }\n\n    m_frame = m_session->m_session->nextFrame(m_session->m_paintCursor);\n\n    m_resource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });\n\n    m_resource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) {\n        if (m_captured) {\n            LOGM(Log::ERR, \"Frame already captured in attach_buffer, {:x}\", (uintptr_t)this);\n            m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, \"already captured\");\n            return;\n        }\n\n        auto PBUFFERRES = CWLBufferResource::fromResource(buf);\n        if (!PBUFFERRES || !PBUFFERRES->m_buffer) {\n            LOGM(Log::ERR, \"Invalid buffer in attach_buffer {:x}\", (uintptr_t)this);\n            m_resource->error(-1, \"invalid buffer\");\n            return;\n        }\n\n        m_buffer = PBUFFERRES->m_buffer.lock();\n    });\n\n    m_resource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) {\n        if (m_captured) {\n            LOGM(Log::ERR, \"Frame already captured in damage_buffer, {:x}\", (uintptr_t)this);\n            m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, \"already captured\");\n            return;\n        }\n\n        if (x < 0 || y < 0 || w <= 0 || h <= 0) {\n            m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, \"invalid buffer damage\");\n            return;\n        }\n\n        m_clientDamage.add(x, y, w, h);\n    });\n\n    m_resource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) {\n        if (m_captured) {\n            LOGM(Log::ERR, \"Frame already captured in capture, {:x}\", (uintptr_t)this);\n            m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, \"already captured\");\n            return;\n        }\n\n        auto error = m_frame->share(m_buffer, m_clientDamage, [this](eScreenshareResult result) {\n            switch (result) {\n                case RESULT_COPIED: m_resource->sendReady(); break;\n                case RESULT_NOT_COPIED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break;\n                case RESULT_TIMESTAMP:\n                    auto [sec, nsec] = Time::secNsec(Time::steadyNow());\n                    uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0;\n                    uint32_t tvSecLo = sec & 0xFFFFFFFF;\n                    m_resource->sendPresentationTime(tvSecHi, tvSecLo, nsec);\n                    break;\n            }\n        });\n\n        switch (error) {\n            case ERROR_NONE: m_captured = true; break;\n            case ERROR_NO_BUFFER: m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, \"no buffer attached\"); break;\n            case ERROR_BUFFER_SIZE:\n            case ERROR_BUFFER_FORMAT: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break;\n            case ERROR_STOPPED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break;\n            case ERROR_UNKNOWN: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break;\n        }\n    });\n\n    m_clientDamage.clear();\n\n    // TODO: see ScreenshareFrame::share() for \"add a damage ring for output damage since last shared frame\"\n    m_resource->sendDamage(0, 0, m_session->m_bufferSize.x, m_session->m_bufferSize.y);\n\n    m_resource->sendTransform(m_frame->transform());\n}\n\nCImageCopyCaptureFrame::~CImageCopyCaptureFrame() {\n    if (m_session)\n        m_session->m_frame.reset();\n}\n\nbool CImageCopyCaptureFrame::good() {\n    return m_resource && m_resource->resource();\n}\n\nCImageCopyCaptureProtocol::CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CImageCopyCaptureProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CExtImageCopyCaptureManagerV1>(client, ver, id));\n\n    if UNLIKELY (!RESOURCE->resource()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    RESOURCE->setDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); });\n    RESOURCE->setOnDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); });\n\n    RESOURCE->setCreateSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, extImageCopyCaptureManagerV1Options options) {\n        auto source = PROTO::imageCaptureSource->sourceFromResource(source_);\n        if (!source) {\n            LOGM(Log::ERR, \"Client tried to create image copy capture session from invalid source\");\n            pMgr->error(-1, \"invalid image capture source\");\n            return;\n        }\n\n        if (options > 1) {\n            LOGM(Log::ERR, \"Client tried to create image copy capture session with invalid options\");\n            pMgr->error(EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_ERROR_INVALID_OPTION, \"Options can't be above 1\");\n            return;\n        }\n\n        auto& PSESSION =\n            m_sessions.emplace_back(makeShared<CImageCopyCaptureSession>(makeShared<CExtImageCopyCaptureSessionV1>(pMgr->client(), pMgr->version(), id), source, options));\n        PSESSION->m_self = PSESSION;\n        LOGM(Log::INFO, \"New image copy capture session for source ({}): \\\"{}\\\"\", source->getTypeName(), source->getName());\n    });\n\n    RESOURCE->setCreatePointerCursorSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, wl_resource* pointer_) {\n        SP<CImageCaptureSource> source = PROTO::imageCaptureSource->sourceFromResource(source_);\n        if (!source) {\n            LOGM(Log::ERR, \"Client tried to create image copy capture session from invalid source\");\n            pMgr->error(-1, \"invalid image capture source\");\n            return;\n        }\n\n        const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(pMgr->client(), PERMISSION_TYPE_CURSOR_POS);\n        if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY)\n            return;\n\n        m_cursorSessions.emplace_back(makeShared<CImageCopyCaptureCursorSession>(makeShared<CExtImageCopyCaptureCursorSessionV1>(pMgr->client(), pMgr->version(), id), source,\n                                                                                 CWLPointerResource::fromResource(pointer_)));\n\n        LOGM(Log::INFO, \"New image copy capture cursor session for source ({}): \\\"{}\\\"\", source->getTypeName(), source->getName());\n    });\n}\n\nvoid CImageCopyCaptureProtocol::destroyResource(CExtImageCopyCaptureManagerV1* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureSession* resource) {\n    std::erase_if(m_sessions, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureCursorSession* resource) {\n    std::erase_if(m_cursorSessions, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureFrame* resource) {\n    std::erase_if(m_frames, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/ImageCopyCapture.hpp",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"../defines.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/Format.hpp\"\n#include \"WaylandProtocol.hpp\"\n#include \"ImageCaptureSource.hpp\"\n#include \"ext-image-copy-capture-v1.hpp\"\n\nclass IHLBuffer;\nclass CWLPointerResource;\nnamespace Screenshare {\n    class CCursorshareSession;\n    class CScreenshareSession;\n    class CScreenshareFrame;\n};\n\nclass CImageCopyCaptureFrame {\n  public:\n    CImageCopyCaptureFrame(SP<CExtImageCopyCaptureFrameV1> resource, WP<CImageCopyCaptureSession> session);\n    ~CImageCopyCaptureFrame();\n\n    bool good();\n\n  private:\n    SP<CExtImageCopyCaptureFrameV1>    m_resource;\n    WP<CImageCopyCaptureSession>       m_session;\n    UP<Screenshare::CScreenshareFrame> m_frame;\n\n    bool                               m_captured = false;\n    SP<IHLBuffer>                      m_buffer;\n    CRegion                            m_clientDamage;\n\n    friend class CImageCopyCaptureSession;\n};\n\nclass CImageCopyCaptureSession {\n  public:\n    CImageCopyCaptureSession(SP<CExtImageCopyCaptureSessionV1> resource, SP<CImageCaptureSource> source, extImageCopyCaptureManagerV1Options options);\n    ~CImageCopyCaptureSession();\n\n    bool good();\n\n  private:\n    SP<CExtImageCopyCaptureSessionV1>    m_resource;\n\n    SP<CImageCaptureSource>              m_source;\n    UP<Screenshare::CScreenshareSession> m_session;\n    WP<CImageCopyCaptureFrame>           m_frame;\n\n    Vector2D                             m_bufferSize  = Vector2D(0, 0);\n    bool                                 m_paintCursor = true;\n\n    struct {\n        CHyprSignalListener constraintsChanged;\n        CHyprSignalListener stopped;\n    } m_listeners;\n\n    WP<CImageCopyCaptureSession> m_self;\n\n    //\n    void sendConstraints();\n\n    friend class CImageCopyCaptureProtocol;\n    friend class CImageCopyCaptureFrame;\n};\n\nclass CImageCopyCaptureCursorSession {\n  public:\n    CImageCopyCaptureCursorSession(SP<CExtImageCopyCaptureCursorSessionV1> resource, SP<CImageCaptureSource> source, SP<CWLPointerResource> pointer);\n    ~CImageCopyCaptureCursorSession();\n\n    bool good();\n\n  private:\n    SP<CExtImageCopyCaptureCursorSessionV1> m_resource;\n    SP<CImageCaptureSource>                 m_source;\n    SP<CWLPointerResource>                  m_pointer;\n\n    // cursor session stuff\n    bool     m_entered = false;\n    Vector2D m_pos     = Vector2D(0, 0);\n    Vector2D m_hotspot = Vector2D(0, 0);\n\n    // capture session stuff\n    SP<CExtImageCopyCaptureSessionV1>    m_sessionResource;\n    UP<Screenshare::CCursorshareSession> m_session;\n    Vector2D                             m_bufferSize = Vector2D(0, 0);\n\n    // frame stuff\n    SP<CExtImageCopyCaptureFrameV1> m_frameResource;\n    bool                            m_captured = false;\n    SP<IHLBuffer>                   m_buffer;\n\n    struct {\n        CHyprSignalListener constraintsChanged;\n        CHyprSignalListener stopped;\n        CHyprSignalListener commit;\n    } m_listeners;\n\n    void sendCursorEvents();\n\n    void createFrame(SP<CExtImageCopyCaptureFrameV1> resource);\n    void destroyCaptureSession();\n    void sendConstraints();\n};\n\nclass CImageCopyCaptureProtocol : public IWaylandProtocol {\n  public:\n    CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         destroyResource(CExtImageCopyCaptureManagerV1* resource);\n    void         destroyResource(CImageCopyCaptureSession* resource);\n    void         destroyResource(CImageCopyCaptureCursorSession* resource);\n    void         destroyResource(CImageCopyCaptureFrame* resource);\n\n  private:\n    std::vector<SP<CExtImageCopyCaptureManagerV1>>  m_managers;\n    std::vector<SP<CImageCopyCaptureSession>>       m_sessions;\n    std::vector<SP<CImageCopyCaptureCursorSession>> m_cursorSessions;\n\n    std::vector<SP<CImageCopyCaptureFrame>>         m_frames;\n\n    friend class CImageCopyCaptureSession;\n};\n\nnamespace PROTO {\n    inline UP<CImageCopyCaptureProtocol> imageCopyCapture;\n};\n"
  },
  {
    "path": "src/protocols/InputMethodV2.cpp",
    "content": "#include \"InputMethodV2.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\n#include <sys/mman.h>\n#include \"core/Compositor.hpp\"\n#include <cstring>\n\nCInputMethodKeyboardGrabV2::CInputMethodKeyboardGrabV2(SP<CZwpInputMethodKeyboardGrabV2> resource_, SP<CInputMethodV2> owner_) : m_resource(resource_), m_owner(owner_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setRelease([this](CZwpInputMethodKeyboardGrabV2* r) { PROTO::ime->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpInputMethodKeyboardGrabV2* r) { PROTO::ime->destroyResource(this); });\n\n    if (!g_pSeatManager->m_keyboard) {\n        LOGM(Log::ERR, \"IME called but no active keyboard???\");\n        return;\n    }\n\n    sendKeyboardData(g_pSeatManager->m_keyboard.lock());\n}\n\nCInputMethodKeyboardGrabV2::~CInputMethodKeyboardGrabV2() {\n    if (!m_owner.expired())\n        std::erase_if(m_owner->m_grabs, [](const auto& g) { return g.expired(); });\n}\n\nvoid CInputMethodKeyboardGrabV2::sendKeyboardData(SP<IKeyboard> keyboard) {\n\n    if (keyboard == m_lastKeyboard)\n        return;\n\n    m_lastKeyboard = keyboard;\n\n    auto keymapFD = allocateSHMFile(keyboard->m_xkbKeymapV1String.length() + 1);\n    if UNLIKELY (!keymapFD.isValid()) {\n        LOGM(Log::ERR, \"Failed to create a keymap file for keyboard grab\");\n        return;\n    }\n\n    void* data = mmap(nullptr, keyboard->m_xkbKeymapV1String.length() + 1, PROT_READ | PROT_WRITE, MAP_SHARED, keymapFD.get(), 0);\n    if UNLIKELY (data == MAP_FAILED) {\n        LOGM(Log::ERR, \"Failed to mmap a keymap file for keyboard grab\");\n        return;\n    }\n\n    memcpy(data, keyboard->m_xkbKeymapV1String.c_str(), keyboard->m_xkbKeymapV1String.length());\n    munmap(data, keyboard->m_xkbKeymapV1String.length() + 1);\n\n    m_resource->sendKeymap(WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymapFD.get(), keyboard->m_xkbKeymapV1String.length() + 1);\n\n    sendMods(keyboard->m_modifiersState.depressed, keyboard->m_modifiersState.latched, keyboard->m_modifiersState.locked, keyboard->m_modifiersState.group);\n\n    m_resource->sendRepeatInfo(keyboard->m_repeatRate, keyboard->m_repeatDelay);\n}\n\nvoid CInputMethodKeyboardGrabV2::sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state) {\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(m_resource->client()));\n\n    m_resource->sendKey(SERIAL, time, key, sc<uint32_t>(state));\n}\n\nvoid CInputMethodKeyboardGrabV2::sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(m_resource->client()));\n\n    m_resource->sendModifiers(SERIAL, depressed, latched, locked, group);\n}\n\nbool CInputMethodKeyboardGrabV2::good() {\n    return m_resource->resource();\n}\n\nSP<CInputMethodV2> CInputMethodKeyboardGrabV2::getOwner() {\n    return m_owner.lock();\n}\n\nwl_client* CInputMethodKeyboardGrabV2::client() {\n    return m_resource->resource() ? m_resource->client() : nullptr;\n}\n\nCInputMethodPopupV2::CInputMethodPopupV2(SP<CZwpInputPopupSurfaceV2> resource_, SP<CInputMethodV2> owner_, SP<CWLSurfaceResource> surface) :\n    m_resource(resource_), m_owner(owner_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CZwpInputPopupSurfaceV2* r) { PROTO::ime->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpInputPopupSurfaceV2* r) { PROTO::ime->destroyResource(this); });\n\n    m_surface = surface;\n\n    m_listeners.destroySurface = surface->m_events.destroy.listen([this] {\n        if (m_mapped)\n            m_events.unmap.emit();\n\n        m_listeners.destroySurface.reset();\n        m_listeners.commitSurface.reset();\n\n        if (Desktop::focusState()->surface() == m_surface)\n            Desktop::focusState()->surface().reset();\n\n        m_surface.reset();\n    });\n\n    m_listeners.commitSurface = surface->m_events.commit.listen([this] {\n        if (m_surface->m_current.texture && !m_mapped) {\n            m_mapped = true;\n            m_surface->map();\n            m_events.map.emit();\n            return;\n        }\n\n        if (!m_surface->m_current.texture && m_mapped) {\n            m_mapped = false;\n            m_surface->unmap();\n            m_events.unmap.emit();\n            return;\n        }\n\n        m_events.commit.emit();\n    });\n}\n\nCInputMethodPopupV2::~CInputMethodPopupV2() {\n    if (!m_owner.expired())\n        std::erase_if(m_owner->m_popups, [](const auto& p) { return p.expired(); });\n\n    m_events.destroy.emit();\n}\n\nbool CInputMethodPopupV2::good() {\n    return m_resource->resource();\n}\n\nvoid CInputMethodPopupV2::sendInputRectangle(const CBox& box) {\n    m_resource->sendTextInputRectangle(box.x, box.y, box.w, box.h);\n}\n\nSP<CWLSurfaceResource> CInputMethodPopupV2::surface() {\n    return m_surface.lock();\n}\n\nvoid CInputMethodV2::SState::reset() {\n    committedString.committed   = false;\n    deleteSurrounding.committed = false;\n    preeditString.committed     = false;\n}\n\nCInputMethodV2::CInputMethodV2(SP<CZwpInputMethodV2> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CZwpInputMethodV2* r) {\n        m_events.destroy.emit();\n        PROTO::ime->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CZwpInputMethodV2* r) {\n        m_events.destroy.emit();\n        PROTO::ime->destroyResource(this);\n    });\n\n    m_resource->setCommitString([this](CZwpInputMethodV2* r, const char* str) {\n        m_pending.committedString.string    = str;\n        m_pending.committedString.committed = true;\n    });\n\n    m_resource->setDeleteSurroundingText([this](CZwpInputMethodV2* r, uint32_t before, uint32_t after) {\n        m_pending.deleteSurrounding.before    = before;\n        m_pending.deleteSurrounding.after     = after;\n        m_pending.deleteSurrounding.committed = true;\n    });\n\n    m_resource->setSetPreeditString([this](CZwpInputMethodV2* r, const char* str, int32_t begin, int32_t end) {\n        m_pending.preeditString.string    = str;\n        m_pending.preeditString.begin     = begin;\n        m_pending.preeditString.end       = end;\n        m_pending.preeditString.committed = true;\n    });\n\n    m_resource->setCommit([this](CZwpInputMethodV2* r, uint32_t serial) {\n        m_current = m_pending;\n        m_pending.reset();\n        m_events.onCommit.emit();\n    });\n\n    m_resource->setGetInputPopupSurface([this](CZwpInputMethodV2* r, uint32_t id, wl_resource* surface) {\n        const auto RESOURCE = PROTO::ime->m_popups.emplace_back(\n            makeShared<CInputMethodPopupV2>(makeShared<CZwpInputPopupSurfaceV2>(r->client(), r->version(), id), m_self.lock(), CWLSurfaceResource::fromResource(surface)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::ime->m_popups.pop_back();\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"New IME Popup with resource id {}\", id);\n\n        m_popups.emplace_back(RESOURCE);\n\n        m_events.newPopup.emit(RESOURCE);\n    });\n\n    m_resource->setGrabKeyboard([this](CZwpInputMethodV2* r, uint32_t id) {\n        const auto RESOURCE =\n            PROTO::ime->m_grabs.emplace_back(makeShared<CInputMethodKeyboardGrabV2>(makeShared<CZwpInputMethodKeyboardGrabV2>(r->client(), r->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::ime->m_grabs.pop_back();\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"New IME Grab with resource id {}\", id);\n\n        m_grabs.emplace_back(RESOURCE);\n    });\n}\n\nCInputMethodV2::~CInputMethodV2() {\n    m_events.destroy.emit();\n}\n\nbool CInputMethodV2::good() {\n    return m_resource->resource();\n}\n\nvoid CInputMethodV2::activate() {\n    if (m_active)\n        return;\n    m_resource->sendActivate();\n    m_active = true;\n}\n\nvoid CInputMethodV2::deactivate() {\n    if (!m_active)\n        return;\n    m_resource->sendDeactivate();\n    m_active = false;\n}\n\nvoid CInputMethodV2::surroundingText(const std::string& text, uint32_t cursor, uint32_t anchor) {\n    m_resource->sendSurroundingText(text.c_str(), cursor, anchor);\n}\n\nvoid CInputMethodV2::textChangeCause(zwpTextInputV3ChangeCause changeCause) {\n    m_resource->sendTextChangeCause(changeCause);\n}\n\nvoid CInputMethodV2::textContentType(zwpTextInputV3ContentHint hint, zwpTextInputV3ContentPurpose purpose) {\n    m_resource->sendContentType(hint, purpose);\n}\n\nvoid CInputMethodV2::done() {\n    m_resource->sendDone();\n}\n\nvoid CInputMethodV2::unavailable() {\n    m_resource->sendUnavailable();\n}\n\nbool CInputMethodV2::hasGrab() {\n    return !m_grabs.empty();\n}\n\nwl_client* CInputMethodV2::grabClient() {\n    if (m_grabs.empty())\n        return nullptr;\n\n    for (auto const& gw : m_grabs) {\n        auto g = gw.lock();\n\n        if (!g)\n            continue;\n\n        return g->client();\n    }\n\n    return nullptr;\n}\n\nvoid CInputMethodV2::sendInputRectangle(const CBox& box) {\n    m_inputRectangle = box;\n    for (auto const& wp : m_popups) {\n        auto p = wp.lock();\n\n        if (!p)\n            continue;\n\n        p->sendInputRectangle(m_inputRectangle);\n    }\n}\n\nvoid CInputMethodV2::sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state) {\n    for (auto const& gw : m_grabs) {\n        auto g = gw.lock();\n\n        if (!g)\n            continue;\n\n        g->sendKey(time, key, state);\n    }\n}\n\nvoid CInputMethodV2::sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {\n    for (auto const& gw : m_grabs) {\n        auto g = gw.lock();\n\n        if (!g)\n            continue;\n\n        g->sendMods(depressed, latched, locked, group);\n    }\n}\n\nvoid CInputMethodV2::setKeyboard(SP<IKeyboard> keyboard) {\n    for (auto const& gw : m_grabs) {\n        auto g = gw.lock();\n\n        if (!g)\n            continue;\n\n        g->sendKeyboardData(keyboard);\n    }\n}\n\nwl_client* CInputMethodV2::client() {\n    return m_resource->client();\n}\n\nCInputMethodV2Protocol::CInputMethodV2Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CInputMethodV2Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpInputMethodManagerV2>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpInputMethodManagerV2* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpInputMethodManagerV2* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetInputMethod([this](CZwpInputMethodManagerV2* pMgr, wl_resource* seat, uint32_t id) { this->onGetIME(pMgr, seat, id); });\n}\n\nvoid CInputMethodV2Protocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CInputMethodV2Protocol::destroyResource(CInputMethodPopupV2* popup) {\n    std::erase_if(m_popups, [&](const auto& other) { return other.get() == popup; });\n}\n\nvoid CInputMethodV2Protocol::destroyResource(CInputMethodKeyboardGrabV2* grab) {\n    std::erase_if(m_grabs, [&](const auto& other) { return other.get() == grab; });\n}\n\nvoid CInputMethodV2Protocol::destroyResource(CInputMethodV2* ime) {\n    std::erase_if(m_imes, [&](const auto& other) { return other.get() == ime; });\n}\n\nvoid CInputMethodV2Protocol::onGetIME(CZwpInputMethodManagerV2* mgr, wl_resource* seat, uint32_t id) {\n    const auto RESOURCE = m_imes.emplace_back(makeShared<CInputMethodV2>(makeShared<CZwpInputMethodV2>(mgr->client(), mgr->version(), id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        mgr->noMemory();\n        m_imes.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self = RESOURCE;\n\n    LOGM(Log::DEBUG, \"New IME with resource id {}\", id);\n\n    m_events.newIME.emit(RESOURCE);\n}\n"
  },
  {
    "path": "src/protocols/InputMethodV2.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"input-method-unstable-v2.hpp\"\n#include \"text-input-unstable-v3.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n\nclass CInputMethodKeyboardGrabV2;\nclass CInputMethodPopupV2;\nclass IKeyboard;\n\nclass CInputMethodV2 {\n  public:\n    CInputMethodV2(SP<CZwpInputMethodV2> resource_);\n    ~CInputMethodV2();\n\n    struct {\n        CSignalT<>                        onCommit;\n        CSignalT<>                        destroy;\n        CSignalT<SP<CInputMethodPopupV2>> newPopup;\n    } m_events;\n\n    struct SState {\n        void reset();\n\n        struct {\n            std::string string;\n            bool        committed = false;\n        } committedString;\n\n        struct {\n            std::string string;\n            int32_t     begin = 0, end = 0;\n            bool        committed = false;\n        } preeditString;\n\n        struct {\n            uint32_t before = 0, after = 0;\n            bool     committed = false;\n        } deleteSurrounding;\n    };\n\n    SState     m_pending;\n    SState     m_current;\n\n    bool       good();\n    void       activate();\n    void       deactivate();\n    void       surroundingText(const std::string& text, uint32_t cursor, uint32_t anchor);\n    void       textChangeCause(zwpTextInputV3ChangeCause changeCause);\n    void       textContentType(zwpTextInputV3ContentHint hint, zwpTextInputV3ContentPurpose purpose);\n    void       done();\n    void       unavailable();\n\n    void       sendInputRectangle(const CBox& box);\n    bool       hasGrab();\n    void       sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state);\n    void       sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group);\n    void       setKeyboard(SP<IKeyboard> keyboard);\n\n    wl_client* client();\n    wl_client* grabClient();\n\n  private:\n    SP<CZwpInputMethodV2>                       m_resource;\n    std::vector<WP<CInputMethodKeyboardGrabV2>> m_grabs;\n    std::vector<WP<CInputMethodPopupV2>>        m_popups;\n\n    WP<CInputMethodV2>                          m_self;\n\n    bool                                        m_active = false;\n\n    CBox                                        m_inputRectangle;\n\n    friend class CInputMethodPopupV2;\n    friend class CInputMethodKeyboardGrabV2;\n    friend class CInputMethodV2Protocol;\n};\n\nclass CInputMethodKeyboardGrabV2 {\n  public:\n    CInputMethodKeyboardGrabV2(SP<CZwpInputMethodKeyboardGrabV2> resource_, SP<CInputMethodV2> owner_);\n    ~CInputMethodKeyboardGrabV2();\n\n    bool               good();\n    SP<CInputMethodV2> getOwner();\n    wl_client*         client();\n\n    void               sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state);\n    void               sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group);\n    void               sendKeyboardData(SP<IKeyboard> keyboard);\n\n  private:\n    SP<CZwpInputMethodKeyboardGrabV2> m_resource;\n    WP<CInputMethodV2>                m_owner;\n\n    WP<IKeyboard>                     m_lastKeyboard;\n};\n\nclass CInputMethodPopupV2 {\n  public:\n    CInputMethodPopupV2(SP<CZwpInputPopupSurfaceV2> resource_, SP<CInputMethodV2> owner_, SP<CWLSurfaceResource> surface);\n    ~CInputMethodPopupV2();\n\n    bool                   good();\n    void                   sendInputRectangle(const CBox& box);\n    SP<CWLSurfaceResource> surface();\n\n    struct {\n        CSignalT<> map;\n        CSignalT<> unmap;\n        CSignalT<> commit;\n        CSignalT<> destroy;\n    } m_events;\n\n    bool m_mapped = false;\n\n  private:\n    SP<CZwpInputPopupSurfaceV2> m_resource;\n    WP<CInputMethodV2>          m_owner;\n    WP<CWLSurfaceResource>      m_surface;\n\n    struct {\n        CHyprSignalListener destroySurface;\n        CHyprSignalListener commitSurface;\n    } m_listeners;\n};\n\nclass CInputMethodV2Protocol : public IWaylandProtocol {\n  public:\n    CInputMethodV2Protocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<SP<CInputMethodV2>> newIME;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CInputMethodPopupV2* popup);\n    void destroyResource(CInputMethodKeyboardGrabV2* grab);\n    void destroyResource(CInputMethodV2* ime);\n\n    void onGetIME(CZwpInputMethodManagerV2* mgr, wl_resource* seat, uint32_t id);\n\n    //\n    std::vector<UP<CZwpInputMethodManagerV2>>   m_managers;\n    std::vector<SP<CInputMethodV2>>             m_imes;\n    std::vector<SP<CInputMethodKeyboardGrabV2>> m_grabs;\n    std::vector<SP<CInputMethodPopupV2>>        m_popups;\n\n    friend class CInputMethodPopupV2;\n    friend class CInputMethodKeyboardGrabV2;\n    friend class CInputMethodV2;\n};\n\nnamespace PROTO {\n    inline UP<CInputMethodV2Protocol> ime;\n};\n"
  },
  {
    "path": "src/protocols/LayerShell.cpp",
    "content": "#include \"LayerShell.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"XDGShell.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"core/Output.hpp\"\n#include \"../helpers/Monitor.hpp\"\n\nvoid CLayerShellResource::SState::reset() {\n    anchor        = 0;\n    exclusive     = 0;\n    interactivity = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;\n    layer         = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;\n    exclusiveEdge = sc<zwlrLayerSurfaceV1Anchor>(0);\n    desiredSize   = {};\n    margin        = {0, 0, 0, 0};\n}\n\nCLayerShellResource::CLayerShellResource(SP<CZwlrLayerSurfaceV1> resource_, SP<CWLSurfaceResource> surf_, std::string namespace_, PHLMONITOR pMonitor,\n                                         zwlrLayerShellV1Layer layer) : m_layerNamespace(namespace_), m_surface(surf_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_current.layer = layer;\n    m_monitor       = pMonitor ? pMonitor->m_name : \"\";\n\n    m_resource->setDestroy([this](CZwlrLayerSurfaceV1* r) {\n        m_events.destroy.emit();\n        PROTO::layerShell->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CZwlrLayerSurfaceV1* r) {\n        m_events.destroy.emit();\n        PROTO::layerShell->destroyResource(this);\n    });\n\n    m_listeners.destroySurface = surf_->m_events.destroy.listen([this] {\n        m_events.destroy.emit();\n        PROTO::layerShell->destroyResource(this);\n    });\n\n    m_listeners.unmapSurface = surf_->m_events.unmap.listen([this] { m_events.unmap.emit(); });\n\n    m_listeners.commitSurface = surf_->m_events.commit.listen([this] {\n        m_current           = m_pending;\n        m_pending.committed = 0;\n\n        bool attachedBuffer = m_surface->m_current.texture;\n\n        if (attachedBuffer && !m_configured) {\n            m_surface->error(-1, \"layerSurface was not configured, but a buffer was attached\");\n            return;\n        }\n\n        constexpr uint32_t horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;\n        constexpr uint32_t vert  = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;\n\n        if (m_current.desiredSize.x <= 0 && (m_current.anchor & horiz) != horiz) {\n            m_surface->error(-1, \"x == 0 but anchor doesn't have left and right\");\n            return;\n        }\n\n        if (m_current.desiredSize.y <= 0 && (m_current.anchor & vert) != vert) {\n            m_surface->error(-1, \"y == 0 but anchor doesn't have top and bottom\");\n            return;\n        }\n\n        if (attachedBuffer && !m_mapped) {\n            m_mapped = true;\n            m_surface->map();\n            m_events.map.emit();\n            return;\n        }\n\n        if (!attachedBuffer && m_mapped) {\n            m_mapped = false;\n            m_events.unmap.emit();\n            m_surface->unmap();\n            m_configured = false;\n            return;\n        }\n\n        m_events.commit.emit();\n    });\n\n    m_resource->setSetSize([this](CZwlrLayerSurfaceV1* r, uint32_t x, uint32_t y) {\n        m_pending.committed |= STATE_SIZE;\n        m_pending.desiredSize = {sc<int>(x), sc<int>(y)};\n    });\n\n    m_resource->setSetAnchor([this](CZwlrLayerSurfaceV1* r, zwlrLayerSurfaceV1Anchor anchor) {\n        if (anchor > (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {\n            r->error(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR, \"Invalid anchor\");\n            return;\n        }\n\n        m_pending.committed |= STATE_ANCHOR;\n        m_pending.anchor = anchor;\n    });\n\n    m_resource->setSetExclusiveZone([this](CZwlrLayerSurfaceV1* r, int32_t zone) {\n        m_pending.committed |= STATE_EXCLUSIVE;\n        m_pending.exclusive = zone;\n    });\n\n    m_resource->setSetMargin([this](CZwlrLayerSurfaceV1* r, int32_t top, int32_t right, int32_t bottom, int32_t left) {\n        m_pending.committed |= STATE_MARGIN;\n        m_pending.margin = {left, right, top, bottom};\n    });\n\n    m_resource->setSetKeyboardInteractivity([this](CZwlrLayerSurfaceV1* r, zwlrLayerSurfaceV1KeyboardInteractivity kbi) {\n        if (kbi > ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) {\n            r->error(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_KEYBOARD_INTERACTIVITY, \"Invalid keyboard interactivity\");\n            return;\n        }\n\n        m_pending.committed |= STATE_INTERACTIVITY;\n        m_pending.interactivity = kbi;\n    });\n\n    m_resource->setGetPopup([this](CZwlrLayerSurfaceV1* r, wl_resource* popup_) {\n        auto popup = CXDGPopupResource::fromResource(popup_);\n\n        if (popup->m_taken) {\n            r->error(-1, \"Parent already exists!\");\n            return;\n        }\n\n        popup->m_taken = true;\n        m_events.newPopup.emit(popup);\n    });\n\n    m_resource->setAckConfigure([this](CZwlrLayerSurfaceV1* r, uint32_t serial) {\n        auto serialFound = std::ranges::find_if(m_serials, [serial](const auto& e) { return e.first == serial; });\n\n        if (serialFound == m_serials.end()) {\n            r->error(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE, \"Serial invalid in ack_configure\");\n            return;\n        }\n\n        m_configured = true;\n        m_size       = serialFound->second;\n\n        m_serials.erase(serialFound);\n    });\n\n    m_resource->setSetLayer([this](CZwlrLayerSurfaceV1* r, uint32_t layer) {\n        if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) {\n            r->error(ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, \"Invalid layer\");\n            return;\n        }\n\n        m_pending.committed |= STATE_LAYER;\n        m_pending.layer = sc<zwlrLayerShellV1Layer>(layer);\n    });\n\n    m_resource->setSetExclusiveEdge([this](CZwlrLayerSurfaceV1* r, zwlrLayerSurfaceV1Anchor anchor) {\n        if (anchor > (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {\n            r->error(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_EXCLUSIVE_EDGE, \"Invalid exclusive edge\");\n            return;\n        }\n\n        if (anchor && (!m_pending.anchor || !(m_pending.anchor & anchor))) {\n            r->error(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_EXCLUSIVE_EDGE, \"Exclusive edge doesn't align with anchor\");\n            return;\n        }\n\n        m_pending.committed |= STATE_EDGE;\n        m_pending.exclusiveEdge = anchor;\n    });\n}\n\nCLayerShellResource::~CLayerShellResource() {\n    m_events.destroy.emit();\n    if (m_surface)\n        m_surface->resetRole();\n}\n\nbool CLayerShellResource::good() {\n    return m_resource->resource();\n}\n\nvoid CLayerShellResource::sendClosed() {\n    if (m_closed)\n        return;\n    m_closed = true;\n    m_resource->sendClosed();\n}\n\nvoid CLayerShellResource::configure(const Vector2D& size_) {\n    m_size = size_;\n\n    auto serial = wl_display_next_serial(g_pCompositor->m_wlDisplay);\n\n    m_serials.push_back({serial, size_});\n\n    m_resource->sendConfigure(serial, size_.x, size_.y);\n}\n\nCLayerShellProtocol::CLayerShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CLayerShellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwlrLayerShellV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwlrLayerShellV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwlrLayerShellV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetLayerSurface([this](CZwlrLayerShellV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* output, zwlrLayerShellV1Layer layer, std::string namespace_) {\n        this->onGetLayerSurface(pMgr, id, surface, output, layer, namespace_);\n    });\n}\n\nvoid CLayerShellProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CLayerShellProtocol::destroyResource(CLayerShellResource* surf) {\n    std::erase_if(m_layers, [&](const auto& other) { return other.get() == surf; });\n}\n\nvoid CLayerShellProtocol::onGetLayerSurface(CZwlrLayerShellV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* output, zwlrLayerShellV1Layer layer, std::string namespace_) {\n    const auto CLIENT   = pMgr->client();\n    const auto PMONITOR = output ? CWLOutputResource::fromResource(output)->m_monitor.lock() : nullptr;\n    auto       SURF     = CWLSurfaceResource::fromResource(surface);\n\n    if UNLIKELY (!SURF) {\n        pMgr->error(-1, \"Invalid surface\");\n        return;\n    }\n\n    if UNLIKELY (SURF->m_role->role() != SURFACE_ROLE_UNASSIGNED) {\n        pMgr->error(-1, \"Surface already has a different role\");\n        return;\n    }\n\n    if UNLIKELY (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) {\n        pMgr->error(ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, \"Invalid layer\");\n        return;\n    }\n\n    const auto RESOURCE = m_layers.emplace_back(makeShared<CLayerShellResource>(makeShared<CZwlrLayerSurfaceV1>(CLIENT, pMgr->version(), id), SURF, namespace_, PMONITOR, layer));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_layers.pop_back();\n        return;\n    }\n\n    SURF->m_role = makeShared<CLayerShellRole>(RESOURCE);\n    g_pCompositor->m_layers.emplace_back(Desktop::View::CLayerSurface::create(RESOURCE));\n\n    LOGM(Log::DEBUG, \"New wlr_layer_surface {:x}\", (uintptr_t)RESOURCE.get());\n}\n\nCLayerShellRole::CLayerShellRole(SP<CLayerShellResource> ls) : m_layerSurface(ls) {\n    ;\n}\n"
  },
  {
    "path": "src/protocols/LayerShell.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include <tuple>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-layer-shell-unstable-v1.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"types/SurfaceRole.hpp\"\n\nclass CMonitor;\nclass CXDGPopupResource;\nclass CWLSurfaceResource;\nclass CLayerShellResource;\n\nclass CLayerShellRole : public ISurfaceRole {\n  public:\n    CLayerShellRole(SP<CLayerShellResource> ls);\n\n    virtual eSurfaceRole role() {\n        return SURFACE_ROLE_LAYER_SHELL;\n    }\n\n    WP<CLayerShellResource> m_layerSurface;\n};\n\nclass CLayerShellResource {\n  public:\n    CLayerShellResource(SP<CZwlrLayerSurfaceV1> resource_, SP<CWLSurfaceResource> surf_, std::string namespace_, PHLMONITOR pMonitor, zwlrLayerShellV1Layer layer);\n    ~CLayerShellResource();\n\n    bool good();\n    void configure(const Vector2D& size);\n    void sendClosed();\n\n    enum eCommittedState : uint8_t {\n        STATE_SIZE          = (1 << 0),\n        STATE_ANCHOR        = (1 << 1),\n        STATE_EXCLUSIVE     = (1 << 2),\n        STATE_MARGIN        = (1 << 3),\n        STATE_INTERACTIVITY = (1 << 4),\n        STATE_LAYER         = (1 << 5),\n        STATE_EDGE          = (1 << 6),\n    };\n\n    struct {\n        CSignalT<>                      destroy;\n        CSignalT<>                      commit;\n        CSignalT<>                      map;\n        CSignalT<>                      unmap;\n        CSignalT<SP<CXDGPopupResource>> newPopup;\n    } m_events;\n\n    struct SState {\n        uint32_t                                anchor    = 0;\n        int32_t                                 exclusive = 0;\n        Vector2D                                desiredSize;\n        zwlrLayerSurfaceV1KeyboardInteractivity interactivity = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;\n        zwlrLayerShellV1Layer                   layer         = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;\n        zwlrLayerSurfaceV1Anchor                exclusiveEdge = sc<zwlrLayerSurfaceV1Anchor>(0);\n        uint32_t                                committed     = 0;\n\n        struct {\n            double left = 0, right = 0, top = 0, bottom = 0;\n        } margin;\n\n        void reset();\n    } m_current, m_pending;\n\n    Vector2D               m_size;\n    std::string            m_layerNamespace;\n    std::string            m_monitor = \"\";\n    WP<CWLSurfaceResource> m_surface;\n    bool                   m_mapped     = false;\n    bool                   m_configured = false;\n\n  private:\n    SP<CZwlrLayerSurfaceV1> m_resource;\n\n    struct {\n        CHyprSignalListener commitSurface;\n        CHyprSignalListener destroySurface;\n        CHyprSignalListener unmapSurface;\n    } m_listeners;\n\n    bool                                       m_closed = false;\n\n    std::vector<std::pair<uint32_t, Vector2D>> m_serials;\n};\n\nclass CLayerShellProtocol : public IWaylandProtocol {\n  public:\n    CLayerShellProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CLayerShellResource* surf);\n    void onGetLayerSurface(CZwlrLayerShellV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* output, zwlrLayerShellV1Layer layer, std::string namespace_);\n\n    //\n    std::vector<UP<CZwlrLayerShellV1>>   m_managers;\n    std::vector<SP<CLayerShellResource>> m_layers;\n\n    friend class CLayerShellResource;\n};\n\nnamespace PROTO {\n    inline UP<CLayerShellProtocol> layerShell;\n};\n"
  },
  {
    "path": "src/protocols/LinuxDMABUF.cpp",
    "content": "#include \"LinuxDMABUF.hpp\"\n#include <algorithm>\n#include <set>\n#include <tuple>\n#include \"../helpers/MiscFunctions.hpp\"\n#include <sys/mman.h>\n#include <xf86drm.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include \"core/Compositor.hpp\"\n#include \"render/Renderer.hpp\"\n#include \"types/DMABuffer.hpp\"\n#include \"types/WLBuffer.hpp\"\n#include \"../render/OpenGL.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../event/EventBus.hpp\"\n\nusing namespace Hyprutils::OS;\n\nstatic std::optional<dev_t> devIDFromFD(int fd) {\n    struct stat stat;\n    if (fstat(fd, &stat) != 0)\n        return {};\n    return stat.st_rdev;\n}\n\nCDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vector<std::pair<PHLMONITORREF, SDMABUFTranche>> tranches_) :\n    m_rendererTranche(_rendererTranche), m_monitorTranches(tranches_) {\n\n    static const auto                       PSKIP_NON_KMS = CConfigValue<Hyprlang::INT>(\"quirks:skip_non_kms_dmabuf_formats\");\n\n    std::vector<SDMABUFFormatTableEntry>    formatsVec;\n    std::set<std::pair<uint32_t, uint64_t>> formats;\n\n    // insert formats into vec if they got inserted into set, meaning they're unique\n    size_t i = 0;\n\n    m_rendererTranche.indices.clear();\n    for (auto const& fmt : m_rendererTranche.formats) {\n        for (auto const& mod : fmt.modifiers) {\n            LOGM(Log::TRACE, \"Render format 0x{:x} ({}) with mod 0x{:x} ({})\", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, NFormatUtils::drmModifierName(mod));\n            if (*PSKIP_NON_KMS && !m_monitorTranches.empty()) {\n                if (std::ranges::none_of(m_monitorTranches, [fmt, mod](const std::pair<PHLMONITORREF, SDMABUFTranche>& pair) {\n                        return std::ranges::any_of(pair.second.formats, [fmt, mod](const SDRMFormat& format) {\n                            return format.drmFormat == fmt.drmFormat && std::ranges::any_of(format.modifiers, [mod](uint64_t modifier) { return mod == modifier; });\n                        });\n                    })) {\n                    LOGM(Log::TRACE, \"    skipped\");\n                    continue;\n                }\n            }\n            auto format        = std::make_pair<>(fmt.drmFormat, mod);\n            auto [_, inserted] = formats.insert(format);\n            if (inserted) {\n                // if it was inserted into set, then its unique and will have a new index in vec\n                m_rendererTranche.indices.push_back(i++);\n                formatsVec.push_back(SDMABUFFormatTableEntry{\n                    .fmt      = fmt.drmFormat,\n                    .modifier = mod,\n                });\n            } else {\n                // if it wasn't inserted then find its index in vec\n                auto it = std::ranges::find_if(formatsVec, [fmt, mod](const SDMABUFFormatTableEntry& oth) { return oth.fmt == fmt.drmFormat && oth.modifier == mod; });\n                m_rendererTranche.indices.push_back(it - formatsVec.begin());\n            }\n        }\n    }\n\n    for (auto& [monitor, tranche] : m_monitorTranches) {\n        tranche.indices.clear();\n        for (auto const& fmt : tranche.formats) {\n            for (auto const& mod : fmt.modifiers) {\n                LOGM(Log::TRACE, \"[DMA] Monitor format 0x{:x} ({}) with mod 0x{:x} ({})\", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod,\n                     NFormatUtils::drmModifierName(mod));\n                // FIXME: recheck this. DRM_FORMAT_MOD_INVALID is allowed by the proto \"For legacy support\". DRM_FORMAT_MOD_LINEAR should be the most compatible mod\n                // apparently these can implode on planes, so don't use them\n                if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR)\n                    continue;\n                auto format        = std::make_pair<>(fmt.drmFormat, mod);\n                auto [_, inserted] = formats.insert(format);\n                if (inserted) {\n                    tranche.indices.push_back(i++);\n                    formatsVec.push_back(SDMABUFFormatTableEntry{\n                        .fmt      = fmt.drmFormat,\n                        .modifier = mod,\n                    });\n                } else {\n                    auto it = std::ranges::find_if(formatsVec, [fmt, mod](const SDMABUFFormatTableEntry& oth) { return oth.fmt == fmt.drmFormat && oth.modifier == mod; });\n                    tranche.indices.push_back(it - formatsVec.begin());\n                }\n            }\n        }\n    }\n\n    m_tableSize = formatsVec.size() * sizeof(SDMABUFFormatTableEntry);\n\n    CFileDescriptor fds[2];\n    allocateSHMFilePair(m_tableSize, fds[0], fds[1]);\n\n    auto arr = sc<SDMABUFFormatTableEntry*>(mmap(nullptr, m_tableSize, PROT_READ | PROT_WRITE, MAP_SHARED, fds[0].get(), 0));\n\n    if (arr == MAP_FAILED) {\n        LOGM(Log::ERR, \"mmap failed\");\n        return;\n    }\n\n    std::ranges::copy(formatsVec, arr);\n\n    munmap(arr, m_tableSize);\n\n    m_tableFD = std::move(fds[1]);\n}\n\nCLinuxDMABuffer::CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs attrs) {\n    m_buffer = makeShared<CDMABuffer>(id, client, attrs);\n\n    m_buffer->m_resource->m_buffer = m_buffer;\n\n    m_listeners.bufferResourceDestroy = m_buffer->events.destroy.listen([this] {\n        m_listeners.bufferResourceDestroy.reset();\n        PROTO::linuxDma->destroyResource(this);\n    });\n\n    if (!m_buffer->m_success)\n        LOGM(Log::ERR, \"Possibly compositor bug: buffer failed to create\");\n}\n\nCLinuxDMABuffer::~CLinuxDMABuffer() {\n    if (m_buffer && m_buffer->m_resource)\n        m_buffer->m_resource->sendRelease();\n\n    m_buffer.reset();\n    m_listeners.bufferResourceDestroy.reset();\n}\n\nbool CLinuxDMABuffer::good() {\n    return m_buffer && m_buffer->good();\n}\n\nCLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UP<CZwpLinuxBufferParamsV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpLinuxBufferParamsV1* r) { PROTO::linuxDma->destroyResource(this); });\n    m_resource->setDestroy([this](CZwpLinuxBufferParamsV1* r) { PROTO::linuxDma->destroyResource(this); });\n\n    m_attrs = makeShared<Aquamarine::SDMABUFAttrs>();\n\n    m_attrs->success = true;\n\n    m_resource->setAdd([this](CZwpLinuxBufferParamsV1* r, int32_t fd, uint32_t plane, uint32_t offset, uint32_t stride, uint32_t modHi, uint32_t modLo) {\n        if (m_used) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, \"Already used\");\n            return;\n        }\n\n        if (plane > 3) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, \"plane > 3\");\n            return;\n        }\n\n        if (m_attrs->fds.at(plane) != -1) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, \"plane used\");\n            return;\n        }\n\n        const uint64_t modifier = (sc<uint64_t>(modHi) << 32) | modLo;\n\n        if (m_resource->version() >= 5 && m_attrs->modifier && m_attrs->modifier != modifier) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, \"planes have different modifiers\");\n            return;\n        }\n\n        m_attrs->fds[plane]     = fd;\n        m_attrs->strides[plane] = stride;\n        m_attrs->offsets[plane] = offset;\n        m_attrs->modifier       = modifier;\n    });\n\n    m_resource->setCreate([this](CZwpLinuxBufferParamsV1* r, int32_t w, int32_t h, uint32_t fmt, zwpLinuxBufferParamsV1Flags flags) {\n        if (m_used) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, \"Already used\");\n            return;\n        }\n\n        if (flags > 0) {\n            r->sendFailed();\n            LOGM(Log::ERR, \"DMABUF flags are not supported\");\n            return;\n        }\n\n        if (m_resource->version() >= 4 && std::ranges::none_of(PROTO::linuxDma->m_formatTable->m_rendererTranche.formats, [this, fmt](const auto format) {\n                return format.drmFormat == fmt && std::ranges::any_of(format.modifiers, [this](const auto mod) { return !mod || mod == m_attrs->modifier; });\n            })) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, \"format + modifier pair is not supported\");\n            return;\n        }\n\n        m_attrs->size   = {w, h};\n        m_attrs->format = fmt;\n        m_attrs->planes = 4 - std::ranges::count(m_attrs->fds, -1);\n\n        create(0);\n    });\n\n    m_resource->setCreateImmed([this](CZwpLinuxBufferParamsV1* r, uint32_t id, int32_t w, int32_t h, uint32_t fmt, zwpLinuxBufferParamsV1Flags flags) {\n        if (m_used) {\n            r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, \"Already used\");\n            return;\n        }\n\n        if (flags > 0) {\n            r->sendFailed();\n            LOGM(Log::ERR, \"DMABUF flags are not supported\");\n            return;\n        }\n\n        m_attrs->size   = {w, h};\n        m_attrs->format = fmt;\n        m_attrs->planes = 4 - std::ranges::count(m_attrs->fds, -1);\n\n        create(id);\n    });\n}\n\nbool CLinuxDMABUFParamsResource::good() {\n    return m_resource->resource();\n}\n\nvoid CLinuxDMABUFParamsResource::create(uint32_t id) {\n    m_used = true;\n\n    if UNLIKELY (!verify()) {\n        LOGM(Log::ERR, \"Failed creating a dmabuf: verify() said no\");\n        return; // if verify failed, we errored the resource.\n    }\n\n    if UNLIKELY (!commence()) {\n        LOGM(Log::ERR, \"Failed creating a dmabuf: commence() said no\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"Creating a dmabuf, with id {}: size {}, fmt {}, planes {}\", id, m_attrs->size, NFormatUtils::drmFormatName(m_attrs->format), m_attrs->planes);\n    for (int i = 0; i < m_attrs->planes; ++i) {\n        LOGM(Log::DEBUG, \" | plane {}: mod {} fd {} stride {} offset {}\", i, m_attrs->modifier, m_attrs->fds[i], m_attrs->strides[i], m_attrs->offsets[i]);\n    }\n\n    auto& buf = PROTO::linuxDma->m_buffers.emplace_back(makeUnique<CLinuxDMABuffer>(id, m_resource->client(), *m_attrs));\n\n    if UNLIKELY (!buf->good() || !buf->m_buffer->m_success) {\n        m_resource->sendFailed();\n        PROTO::linuxDma->m_buffers.pop_back();\n        return;\n    }\n\n    if (!id)\n        m_resource->sendCreated(buf->m_buffer->m_resource->getResource());\n\n    m_createdBuffer = buf;\n}\n\nbool CLinuxDMABUFParamsResource::commence() {\n    if (!PROTO::linuxDma->m_mainDeviceFD.isValid())\n        return true;\n\n    for (int i = 0; i < m_attrs->planes; i++) {\n        uint32_t handle = 0;\n\n        if (drmPrimeFDToHandle(PROTO::linuxDma->m_mainDeviceFD.get(), m_attrs->fds.at(i), &handle)) {\n            LOGM(Log::ERR, \"Failed to import dmabuf fd\");\n            return false;\n        }\n\n        if (drmCloseBufferHandle(PROTO::linuxDma->m_mainDeviceFD.get(), handle)) {\n            LOGM(Log::ERR, \"Failed to close dmabuf handle\");\n            return false;\n        }\n    }\n\n    return true;\n}\n\nbool CLinuxDMABUFParamsResource::verify() {\n    if UNLIKELY (m_attrs->planes <= 0) {\n        m_resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, \"No planes added\");\n        return false;\n    }\n\n    if UNLIKELY (m_attrs->fds.at(0) < 0) {\n        m_resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, \"No plane 0\");\n        return false;\n    }\n\n    bool empty = false;\n    for (auto const& plane : m_attrs->fds) {\n        if (empty && plane != -1) {\n            m_resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, \"Gap in planes\");\n            return false;\n        }\n\n        if (plane == -1) {\n            empty = true;\n            continue;\n        }\n    }\n\n    if UNLIKELY (m_attrs->size.x < 1 || m_attrs->size.y < 1) {\n        m_resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, \"x/y < 1\");\n        return false;\n    }\n\n    for (size_t i = 0; i < sc<size_t>(m_attrs->planes); ++i) {\n        if (sc<uint64_t>(m_attrs->offsets.at(i)) + sc<uint64_t>(m_attrs->strides.at(i)) * m_attrs->size.y > UINT32_MAX) {\n            m_resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,\n                              std::format(\"size overflow on plane {}: offset {} + stride {} * height {} = {}, overflows UINT32_MAX\", i, sc<uint64_t>(m_attrs->offsets.at(i)),\n                                          sc<uint64_t>(m_attrs->strides.at(i)), m_attrs->size.y, sc<uint64_t>(m_attrs->offsets.at(i)) + sc<uint64_t>(m_attrs->strides.at(i))));\n            return false;\n        }\n    }\n\n    return true;\n}\n\nCLinuxDMABUFFeedbackResource::CLinuxDMABUFFeedbackResource(UP<CZwpLinuxDmabufFeedbackV1>&& resource_, SP<CWLSurfaceResource> surface_) :\n    m_surface(surface_), m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpLinuxDmabufFeedbackV1* r) { PROTO::linuxDma->destroyResource(this); });\n    m_resource->setDestroy([this](CZwpLinuxDmabufFeedbackV1* r) { PROTO::linuxDma->destroyResource(this); });\n\n    auto& formatTable = PROTO::linuxDma->m_formatTable;\n    m_resource->sendFormatTable(formatTable->m_tableFD.get(), formatTable->m_tableSize);\n    sendDefaultFeedback();\n}\n\nbool CLinuxDMABUFFeedbackResource::good() {\n    return m_resource->resource();\n}\n\nvoid CLinuxDMABUFFeedbackResource::sendTranche(SDMABUFTranche& tranche) {\n    struct wl_array deviceArr = {\n        .size = sizeof(tranche.device),\n        .data = sc<void*>(&tranche.device),\n    };\n    m_resource->sendTrancheTargetDevice(&deviceArr);\n\n    m_resource->sendTrancheFlags(sc<zwpLinuxDmabufFeedbackV1TrancheFlags>(tranche.flags));\n\n    wl_array indices = {\n        .size = tranche.indices.size() * sizeof(tranche.indices.at(0)),\n        .data = tranche.indices.data(),\n    };\n    m_resource->sendTrancheFormats(&indices);\n    m_resource->sendTrancheDone();\n}\n\n// default tranche is based on renderer (egl)\nvoid CLinuxDMABUFFeedbackResource::sendDefaultFeedback() {\n    auto            mainDevice  = PROTO::linuxDma->m_mainDevice;\n    auto&           formatTable = PROTO::linuxDma->m_formatTable;\n\n    struct wl_array deviceArr = {\n        .size = sizeof(mainDevice),\n        .data = sc<void*>(&mainDevice),\n    };\n    m_resource->sendMainDevice(&deviceArr);\n\n    sendTranche(formatTable->m_rendererTranche);\n\n    m_resource->sendDone();\n\n    m_lastFeedbackWasScanout = false;\n}\n\nCLinuxDMABUFResource::CLinuxDMABUFResource(UP<CZwpLinuxDmabufV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpLinuxDmabufV1* r) { PROTO::linuxDma->destroyResource(this); });\n    m_resource->setDestroy([this](CZwpLinuxDmabufV1* r) { PROTO::linuxDma->destroyResource(this); });\n\n    m_resource->setGetDefaultFeedback([](CZwpLinuxDmabufV1* r, uint32_t id) {\n        const auto& RESOURCE =\n            PROTO::linuxDma->m_feedbacks.emplace_back(makeUnique<CLinuxDMABUFFeedbackResource>(makeUnique<CZwpLinuxDmabufFeedbackV1>(r->client(), r->version(), id), nullptr));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::linuxDma->m_feedbacks.pop_back();\n            return;\n        }\n    });\n\n    m_resource->setGetSurfaceFeedback([](CZwpLinuxDmabufV1* r, uint32_t id, wl_resource* surf) {\n        const auto& RESOURCE = PROTO::linuxDma->m_feedbacks.emplace_back(\n            makeUnique<CLinuxDMABUFFeedbackResource>(makeUnique<CZwpLinuxDmabufFeedbackV1>(r->client(), r->version(), id), CWLSurfaceResource::fromResource(surf)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::linuxDma->m_feedbacks.pop_back();\n            return;\n        }\n    });\n\n    m_resource->setCreateParams([](CZwpLinuxDmabufV1* r, uint32_t id) {\n        const auto& RESOURCE = PROTO::linuxDma->m_params.emplace_back(makeUnique<CLinuxDMABUFParamsResource>(makeUnique<CZwpLinuxBufferParamsV1>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::linuxDma->m_params.pop_back();\n            return;\n        }\n    });\n\n    sendMods();\n}\n\nbool CLinuxDMABUFResource::good() {\n    return m_resource->resource();\n}\n\nvoid CLinuxDMABUFResource::sendMods() {\n    for (auto const& fmt : PROTO::linuxDma->m_formatTable->m_rendererTranche.formats) {\n        m_resource->sendFormat(fmt.drmFormat);\n\n        if (m_resource->version() == 3) {\n            for (auto const& mod : fmt.modifiers) {\n                // TODO: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1166\n\n                m_resource->sendModifier(fmt.drmFormat, mod >> 32, mod & 0xFFFFFFFF);\n            }\n        }\n    }\n}\n\nCLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.ready.listen([this] {\n        int  rendererFD = g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd;\n        auto dev        = devIDFromFD(rendererFD);\n\n        if (!dev.has_value()) {\n            LOGM(Log::ERR, \"failed to get drm dev, disabling linux dmabuf\");\n            removeGlobal();\n            return;\n        }\n\n        m_mainDevice = *dev;\n\n        SDMABUFTranche eglTranche = {\n            .device  = m_mainDevice,\n            .flags   = 0, // renderer isn't for ds so don't set flag.\n            .formats = g_pHyprRenderer->getDRMFormats(),\n        };\n\n        std::vector<std::pair<PHLMONITORREF, SDMABUFTranche>> tches;\n\n        if (g_pCompositor->m_aqBackend->hasSession()) {\n            // this assumes there's only 1 device used for both scanout and rendering\n            // also that each monitor never changes its primary plane\n\n            for (auto const& mon : g_pCompositor->m_monitors) {\n                auto tranche = SDMABUFTranche{\n                    .device  = m_mainDevice,\n                    .flags   = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT,\n                    .formats = mon->m_output->getRenderFormats(),\n                };\n                tches.emplace_back(std::make_pair<>(mon, tranche));\n            }\n\n            static auto monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR mon) {\n                auto tranche = SDMABUFTranche{\n                    .device  = m_mainDevice,\n                    .flags   = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT,\n                    .formats = mon->m_output->getRenderFormats(),\n                };\n                m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(mon, tranche));\n                resetFormatTable();\n            });\n\n            static auto monitorRemoved = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR mon) {\n                std::erase_if(m_formatTable->m_monitorTranches, [mon](std::pair<PHLMONITORREF, SDMABUFTranche> pair) { return pair.first == mon; });\n                resetFormatTable();\n            });\n\n            static auto configReloaded = Event::bus()->m_events.config.reloaded.listen([this] {\n                static const auto PSKIP_NON_KMS = CConfigValue<Hyprlang::INT>(\"quirks:skip_non_kms_dmabuf_formats\");\n                static auto       prev          = *PSKIP_NON_KMS;\n                if (prev != *PSKIP_NON_KMS) {\n                    prev = *PSKIP_NON_KMS;\n                    resetFormatTable();\n                }\n            });\n        }\n\n        m_formatTable = makeUnique<CDMABUFFormatTable>(eglTranche, tches);\n\n        drmDevice* device = nullptr;\n        if (drmGetDeviceFromDevId(m_mainDevice, 0, &device) != 0) {\n            LOGM(Log::ERR, \"failed to get drm dev, disabling linux dmabuf\");\n            removeGlobal();\n            return;\n        }\n\n        if (g_pCompositor->m_drmRenderNode.fd >= 0 && rendererFD == g_pCompositor->m_drmRenderNode.fd) {\n            // Already using the compositor's render node, reuse it.\n            m_mainDeviceFD = CFileDescriptor{fcntl(g_pCompositor->m_drmRenderNode.fd, F_DUPFD_CLOEXEC, 0)};\n            drmFreeDevice(&device);\n            if (!m_mainDeviceFD.isValid()) {\n                LOGM(Log::ERR, \"failed to open rendernode, disabling linux dmabuf\");\n                removeGlobal();\n                return;\n            }\n\n            return; // already using rendernode.\n        }\n\n        if (device->available_nodes & (1 << DRM_NODE_RENDER)) {\n            const char* name = device->nodes[DRM_NODE_RENDER];\n            m_mainDeviceFD   = CFileDescriptor{open(name, O_RDWR | O_CLOEXEC)};\n            drmFreeDevice(&device);\n            if (!m_mainDeviceFD.isValid()) {\n                LOGM(Log::ERR, \"failed to open drm dev, disabling linux dmabuf\");\n                removeGlobal();\n                return;\n            }\n        } else {\n            LOGM(Log::ERR, \"DRM device {} has no render node, disabling linux dmabuf checks\", device->nodes[DRM_NODE_PRIMARY] ? device->nodes[DRM_NODE_PRIMARY] : \"null\");\n            drmFreeDevice(&device);\n        }\n    });\n}\n\nvoid CLinuxDMABufV1Protocol::resetFormatTable() {\n    if (!m_formatTable)\n        return;\n\n    LOGM(Log::DEBUG, \"Resetting format table\");\n\n    // this might be a big copy\n    auto newFormatTable = makeUnique<CDMABUFFormatTable>(m_formatTable->m_rendererTranche, m_formatTable->m_monitorTranches);\n\n    for (auto const& feedback : m_feedbacks) {\n        feedback->m_resource->sendFormatTable(newFormatTable->m_tableFD.get(), newFormatTable->m_tableSize);\n        if (feedback->m_lastFeedbackWasScanout) {\n            PHLMONITOR mon;\n            auto       HLSurface = Desktop::View::CWLSurface::fromResource(feedback->m_surface);\n            if (!HLSurface) {\n                feedback->sendDefaultFeedback();\n                continue;\n            }\n            if (auto w = Desktop::View::CWindow::fromView(HLSurface->view()); w)\n                if (auto m = w->m_monitor.lock(); m)\n                    mon = m->m_self.lock();\n\n            if (!mon) {\n                feedback->sendDefaultFeedback();\n                continue;\n            }\n\n            updateScanoutTranche(feedback->m_surface, mon);\n        } else {\n            feedback->sendDefaultFeedback();\n        }\n    }\n\n    // delete old table after we sent new one\n    m_formatTable = std::move(newFormatTable);\n}\n\nvoid CLinuxDMABufV1Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto& RESOURCE = m_managers.emplace_back(makeUnique<CLinuxDMABUFResource>(makeUnique<CZwpLinuxDmabufV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CLinuxDMABufV1Protocol::destroyResource(CLinuxDMABUFResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CLinuxDMABufV1Protocol::destroyResource(CLinuxDMABUFFeedbackResource* resource) {\n    std::erase_if(m_feedbacks, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CLinuxDMABufV1Protocol::destroyResource(CLinuxDMABUFParamsResource* resource) {\n    std::erase_if(m_params, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CLinuxDMABufV1Protocol::destroyResource(CLinuxDMABuffer* resource) {\n    std::erase_if(m_buffers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CLinuxDMABufV1Protocol::updateScanoutTranche(SP<CWLSurfaceResource> surface, PHLMONITOR pMonitor) {\n    WP<CLinuxDMABUFFeedbackResource> feedbackResource;\n    for (auto const& f : m_feedbacks) {\n        if (f->m_surface != surface)\n            continue;\n\n        feedbackResource = f;\n        break;\n    }\n\n    if (!feedbackResource) {\n        LOGM(Log::DEBUG, \"updateScanoutTranche: surface has no dmabuf_feedback\");\n        return;\n    }\n\n    if (!pMonitor) {\n        LOGM(Log::DEBUG, \"updateScanoutTranche: resetting feedback\");\n        feedbackResource->sendDefaultFeedback();\n        return;\n    }\n\n    const auto& monitorTranchePair =\n        std::ranges::find_if(m_formatTable->m_monitorTranches, [pMonitor](std::pair<PHLMONITORREF, SDMABUFTranche> pair) { return pair.first == pMonitor; });\n\n    if (monitorTranchePair == m_formatTable->m_monitorTranches.end()) {\n        LOGM(Log::DEBUG, \"updateScanoutTranche: monitor has no tranche\");\n        return;\n    }\n\n    auto& monitorTranche = (*monitorTranchePair).second;\n\n    LOGM(Log::DEBUG, \"updateScanoutTranche: sending a scanout tranche\");\n\n    struct wl_array deviceArr = {\n        .size = sizeof(m_mainDevice),\n        .data = sc<void*>(&m_mainDevice),\n    };\n    feedbackResource->m_resource->sendMainDevice(&deviceArr);\n\n    // prioritize scnaout tranche but have renderer fallback tranche\n    // also yes formats can be duped here because different tranche flags (ds and no ds)\n    feedbackResource->sendTranche(monitorTranche);\n    feedbackResource->sendTranche(m_formatTable->m_rendererTranche);\n\n    feedbackResource->m_resource->sendDone();\n\n    feedbackResource->m_lastFeedbackWasScanout = true;\n}\n\ndev_t CLinuxDMABufV1Protocol::getMainDevice() {\n    return m_mainDevice;\n}\n"
  },
  {
    "path": "src/protocols/LinuxDMABUF.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"wayland.hpp\"\n#include \"linux-dmabuf-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/Format.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CDMABuffer;\nclass CWLSurfaceResource;\n\nclass CLinuxDMABuffer {\n  public:\n    CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs attrs);\n    ~CLinuxDMABuffer();\n\n    bool good();\n\n  private:\n    SP<CDMABuffer> m_buffer;\n\n    struct {\n        CHyprSignalListener bufferResourceDestroy;\n    } m_listeners;\n\n    friend class CLinuxDMABUFParamsResource;\n};\n\n#pragma pack(push, 1)\nstruct SDMABUFFormatTableEntry {\n    uint32_t fmt = 0;\n    char     pad[4];\n    uint64_t modifier = 0;\n};\n#pragma pack(pop)\n\nstruct SDMABUFTranche {\n    dev_t                   device = 0;\n    uint32_t                flags  = 0;\n    std::vector<SDRMFormat> formats;\n    std::vector<uint16_t>   indices;\n};\n\nclass CDMABUFFormatTable {\n  public:\n    CDMABUFFormatTable(SDMABUFTranche rendererTranche, std::vector<std::pair<PHLMONITORREF, SDMABUFTranche>> tranches);\n    ~CDMABUFFormatTable() = default;\n\n    Hyprutils::OS::CFileDescriptor                        m_tableFD;\n    size_t                                                m_tableSize = 0;\n    SDMABUFTranche                                        m_rendererTranche;\n    std::vector<std::pair<PHLMONITORREF, SDMABUFTranche>> m_monitorTranches;\n};\n\nclass CLinuxDMABUFParamsResource {\n  public:\n    CLinuxDMABUFParamsResource(UP<CZwpLinuxBufferParamsV1>&& resource_);\n    ~CLinuxDMABUFParamsResource() = default;\n\n    bool                         good();\n    void                         create(uint32_t id); // 0 means not immed\n\n    SP<Aquamarine::SDMABUFAttrs> m_attrs;\n    WP<CLinuxDMABuffer>          m_createdBuffer;\n    bool                         m_used = false;\n\n  private:\n    UP<CZwpLinuxBufferParamsV1> m_resource;\n\n    bool                        verify();\n    bool                        commence();\n};\n\nclass CLinuxDMABUFFeedbackResource {\n  public:\n    CLinuxDMABUFFeedbackResource(UP<CZwpLinuxDmabufFeedbackV1>&& resource_, SP<CWLSurfaceResource> surface_);\n    ~CLinuxDMABUFFeedbackResource() = default;\n\n    bool                   good();\n    void                   sendDefaultFeedback();\n    void                   sendTranche(SDMABUFTranche& tranche);\n\n    SP<CWLSurfaceResource> m_surface; // optional, for surface feedbacks\n\n  private:\n    UP<CZwpLinuxDmabufFeedbackV1> m_resource;\n    bool                          m_lastFeedbackWasScanout = false;\n\n    friend class CLinuxDMABufV1Protocol;\n};\n\nclass CLinuxDMABUFResource {\n  public:\n    CLinuxDMABUFResource(UP<CZwpLinuxDmabufV1>&& resource_);\n    ~CLinuxDMABUFResource() = default;\n\n    bool good();\n    void sendMods();\n\n  private:\n    UP<CZwpLinuxDmabufV1> m_resource;\n};\n\nclass CLinuxDMABufV1Protocol : public IWaylandProtocol {\n  public:\n    CLinuxDMABufV1Protocol(const wl_interface* iface, const int& ver, const std::string& name);\n    ~CLinuxDMABufV1Protocol() = default;\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n    void         updateScanoutTranche(SP<CWLSurfaceResource> surface, PHLMONITOR pMonitor);\n    dev_t        getMainDevice();\n\n  private:\n    void destroyResource(CLinuxDMABUFResource* resource);\n    void destroyResource(CLinuxDMABUFFeedbackResource* resource);\n    void destroyResource(CLinuxDMABUFParamsResource* resource);\n    void destroyResource(CLinuxDMABuffer* resource);\n\n    void resetFormatTable();\n\n    //\n    std::vector<UP<CLinuxDMABUFResource>>         m_managers;\n    std::vector<UP<CLinuxDMABUFFeedbackResource>> m_feedbacks;\n    std::vector<UP<CLinuxDMABUFParamsResource>>   m_params;\n    std::vector<UP<CLinuxDMABuffer>>              m_buffers;\n\n    UP<CDMABUFFormatTable>                        m_formatTable;\n    dev_t                                         m_mainDevice;\n    Hyprutils::OS::CFileDescriptor                m_mainDeviceFD;\n\n    friend class CLinuxDMABUFResource;\n    friend class CLinuxDMABUFFeedbackResource;\n    friend class CLinuxDMABUFParamsResource;\n    friend class CLinuxDMABuffer;\n};\n\nnamespace PROTO {\n    inline UP<CLinuxDMABufV1Protocol> linuxDma;\n};\n"
  },
  {
    "path": "src/protocols/LockNotify.cpp",
    "content": "#include \"LockNotify.hpp\"\n\nCHyprlandLockNotification::CHyprlandLockNotification(SP<CHyprlandLockNotificationV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CHyprlandLockNotificationV1* r) { PROTO::lockNotify->destroyNotification(this); });\n    m_resource->setOnDestroy([this](CHyprlandLockNotificationV1* r) { PROTO::lockNotify->destroyNotification(this); });\n}\n\nbool CHyprlandLockNotification::good() {\n    return m_resource->resource();\n}\n\nvoid CHyprlandLockNotification::onLocked() {\n    if LIKELY (!m_locked)\n        m_resource->sendLocked();\n\n    m_locked = true;\n}\n\nvoid CHyprlandLockNotification::onUnlocked() {\n    if LIKELY (m_locked)\n        m_resource->sendUnlocked();\n\n    m_locked = false;\n}\n\nCLockNotifyProtocol::CLockNotifyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CLockNotifyProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CHyprlandLockNotifierV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CHyprlandLockNotifierV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CHyprlandLockNotifierV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetLockNotification([this](CHyprlandLockNotifierV1* pMgr, uint32_t id) { this->onGetNotification(pMgr, id); });\n}\n\nvoid CLockNotifyProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CLockNotifyProtocol::destroyNotification(CHyprlandLockNotification* notif) {\n    std::erase_if(m_notifications, [&](const auto& other) { return other.get() == notif; });\n}\n\nvoid CLockNotifyProtocol::onGetNotification(CHyprlandLockNotifierV1* pMgr, uint32_t id) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_notifications.emplace_back(makeShared<CHyprlandLockNotification>(makeShared<CHyprlandLockNotificationV1>(CLIENT, pMgr->version(), id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_notifications.pop_back();\n        return;\n    }\n\n    // Already locked?? Send locked right away\n    if UNLIKELY (m_isLocked)\n        m_notifications.back()->onLocked();\n}\n\nvoid CLockNotifyProtocol::onLocked() {\n    if UNLIKELY (m_isLocked) {\n        LOGM(Log::ERR, \"Not sending lock notification. Already locked!\");\n        return;\n    }\n\n    for (auto const& n : m_notifications) {\n        n->onLocked();\n    }\n\n    m_isLocked = true;\n}\n\nvoid CLockNotifyProtocol::onUnlocked() {\n    if UNLIKELY (!m_isLocked) {\n        LOGM(Log::ERR, \"Not sending unlock notification. Not locked!\");\n        return;\n    }\n\n    for (auto const& n : m_notifications) {\n        n->onUnlocked();\n    }\n\n    m_isLocked = false;\n}\n"
  },
  {
    "path": "src/protocols/LockNotify.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"hyprland-lock-notify-v1.hpp\"\n\nclass CEventLoopTimer;\n\nclass CHyprlandLockNotification {\n  public:\n    CHyprlandLockNotification(SP<CHyprlandLockNotificationV1> resource_);\n    ~CHyprlandLockNotification() = default;\n\n    bool good();\n    void onLocked();\n    void onUnlocked();\n\n  private:\n    SP<CHyprlandLockNotificationV1> m_resource;\n    bool                            m_locked = false;\n};\n\nclass CLockNotifyProtocol : public IWaylandProtocol {\n  public:\n    CLockNotifyProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         onLocked();\n    void         onUnlocked();\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyNotification(CHyprlandLockNotification* notif);\n    void onGetNotification(CHyprlandLockNotifierV1* pMgr, uint32_t id);\n\n    bool m_isLocked = false;\n\n    //\n    std::vector<UP<CHyprlandLockNotifierV1>>   m_managers;\n    std::vector<SP<CHyprlandLockNotification>> m_notifications;\n\n    friend class CHyprlandLockNotification;\n};\n\nnamespace PROTO {\n    inline UP<CLockNotifyProtocol> lockNotify;\n};\n"
  },
  {
    "path": "src/protocols/MesaDRM.cpp",
    "content": "#include \"MesaDRM.hpp\"\n#include <algorithm>\n#include <xf86drm.h>\n#include \"../Compositor.hpp\"\n#include \"render/Renderer.hpp\"\n#include \"types/WLBuffer.hpp\"\n#include \"../render/OpenGL.hpp\"\n\nCMesaDRMBufferResource::CMesaDRMBufferResource(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs attrs_) {\n    LOGM(Log::DEBUG, \"Creating a Mesa dmabuf, with id {}: size {}, fmt {}, planes {}\", id, attrs_.size, attrs_.format, attrs_.planes);\n    for (int i = 0; i < attrs_.planes; ++i) {\n        LOGM(Log::DEBUG, \" | plane {}: mod {} fd {} stride {} offset {}\", i, attrs_.modifier, attrs_.fds[i], attrs_.strides[i], attrs_.offsets[i]);\n    }\n\n    m_buffer                       = makeShared<CDMABuffer>(id, client, attrs_);\n    m_buffer->m_resource->m_buffer = m_buffer;\n\n    m_listeners.bufferResourceDestroy = m_buffer->events.destroy.listen([this] {\n        m_listeners.bufferResourceDestroy.reset();\n        PROTO::mesaDRM->destroyResource(this);\n    });\n\n    if (!m_buffer->m_success)\n        LOGM(Log::ERR, \"Possibly compositor bug: buffer failed to create\");\n}\n\nCMesaDRMBufferResource::~CMesaDRMBufferResource() {\n    if (m_buffer && m_buffer->m_resource)\n        m_buffer->m_resource->sendRelease();\n    m_buffer.reset();\n    m_listeners.bufferResourceDestroy.reset();\n}\n\nbool CMesaDRMBufferResource::good() {\n    return m_buffer && m_buffer->good();\n}\n\nCMesaDRMResource::CMesaDRMResource(SP<CWlDrm> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlDrm* r) { PROTO::mesaDRM->destroyResource(this); });\n\n    m_resource->setAuthenticate([this](CWlDrm* r, uint32_t token) {\n        // we don't need this\n        m_resource->sendAuthenticated();\n    });\n\n    m_resource->setCreateBuffer(\n        [](CWlDrm* r, uint32_t, uint32_t, int32_t, int32_t, uint32_t, uint32_t) { r->error(WL_DRM_ERROR_INVALID_NAME, \"Not supported, use prime instead\"); });\n\n    m_resource->setCreatePlanarBuffer([](CWlDrm* r, uint32_t, uint32_t, int32_t, int32_t, uint32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) {\n        r->error(WL_DRM_ERROR_INVALID_NAME, \"Not supported, use prime instead\");\n    });\n\n    m_resource->setCreatePrimeBuffer(\n        [this](CWlDrm* r, uint32_t id, int32_t nameFd, int32_t w, int32_t h, uint32_t fmt, int32_t off0, int32_t str0, int32_t off1, int32_t str1, int32_t off2, int32_t str2) {\n            if (off0 < 0 || w <= 0 || h <= 0) {\n                r->error(WL_DRM_ERROR_INVALID_FORMAT, \"Invalid w, h, or offset\");\n                return;\n            }\n\n            uint64_t mod = DRM_FORMAT_MOD_INVALID;\n\n            auto     fmts = g_pHyprRenderer->getDRMFormats();\n            for (auto const& f : fmts) {\n                if (f.drmFormat != fmt)\n                    continue;\n\n                for (auto const& m : f.modifiers) {\n                    if (m == DRM_FORMAT_MOD_LINEAR)\n                        continue;\n\n                    mod = m;\n                    break;\n                }\n                break;\n            }\n\n            Aquamarine::SDMABUFAttrs attrs;\n            attrs.success    = true;\n            attrs.size       = {w, h};\n            attrs.modifier   = mod;\n            attrs.planes     = 1;\n            attrs.offsets[0] = off0;\n            attrs.strides[0] = str0;\n            attrs.fds[0]     = nameFd;\n            attrs.format     = fmt;\n\n            const auto RESOURCE = PROTO::mesaDRM->m_buffers.emplace_back(makeShared<CMesaDRMBufferResource>(id, m_resource->client(), attrs));\n\n            if UNLIKELY (!RESOURCE->good()) {\n                r->noMemory();\n                PROTO::mesaDRM->m_buffers.pop_back();\n                return;\n            }\n\n            // append instance so that buffer knows its owner\n            RESOURCE->m_buffer->m_resource->m_buffer = RESOURCE->m_buffer;\n        });\n\n    m_resource->sendDevice(PROTO::mesaDRM->m_nodeName.c_str());\n    m_resource->sendCapabilities(WL_DRM_CAPABILITY_PRIME);\n\n    auto fmts = g_pHyprRenderer->getDRMFormats();\n    for (auto const& fmt : fmts) {\n        m_resource->sendFormat(fmt.drmFormat);\n    }\n}\n\nbool CMesaDRMResource::good() {\n    return m_resource->resource();\n}\n\nCMesaDRMProtocol::CMesaDRMProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    drmDevice* dev   = nullptr;\n    int        drmFD = g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd;\n\n    if (drmGetDevice2(drmFD, 0, &dev) != 0) {\n        LOGM(Log::ERR, \"Failed to get device from fd {}, disabling MesaDRM\", drmFD);\n        removeGlobal();\n        return;\n    }\n\n    if (dev->available_nodes & (1 << DRM_NODE_RENDER) && dev->nodes[DRM_NODE_RENDER]) {\n        m_nodeName = dev->nodes[DRM_NODE_RENDER];\n    } else if (dev->available_nodes & (1 << DRM_NODE_PRIMARY) && dev->nodes[DRM_NODE_PRIMARY]) {\n        LOGM(Log::WARN, \"No DRM render node, falling back to primary {}\", dev->nodes[DRM_NODE_PRIMARY]);\n        m_nodeName = dev->nodes[DRM_NODE_PRIMARY];\n    } else {\n        LOGM(Log::ERR, \"No usable DRM node (render or primary) found, disabling MesaDRM\");\n        drmFreeDevice(&dev);\n        removeGlobal();\n        return;\n    }\n\n    drmFreeDevice(&dev);\n}\n\nvoid CMesaDRMProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CMesaDRMResource>(makeShared<CWlDrm>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CMesaDRMProtocol::destroyResource(CMesaDRMResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CMesaDRMProtocol::destroyResource(CMesaDRMBufferResource* resource) {\n    std::erase_if(m_buffers, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/MesaDRM.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"wayland-drm.hpp\"\n#include \"types/Buffer.hpp\"\n#include \"types/DMABuffer.hpp\"\n\nclass CMesaDRMBufferResource {\n  public:\n    CMesaDRMBufferResource(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs attrs);\n    ~CMesaDRMBufferResource();\n\n    bool good();\n\n  private:\n    SP<CDMABuffer> m_buffer;\n\n    struct {\n        CHyprSignalListener bufferResourceDestroy;\n    } m_listeners;\n\n    friend class CMesaDRMResource;\n};\n\nclass CMesaDRMResource {\n  public:\n    CMesaDRMResource(SP<CWlDrm> resource_);\n\n    bool good();\n\n  private:\n    SP<CWlDrm> m_resource;\n};\n\nclass CMesaDRMProtocol : public IWaylandProtocol {\n  public:\n    CMesaDRMProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CMesaDRMResource* resource);\n    void destroyResource(CMesaDRMBufferResource* resource);\n\n    //\n    std::vector<SP<CMesaDRMResource>>       m_managers;\n    std::vector<SP<CMesaDRMBufferResource>> m_buffers;\n\n    std::string                             m_nodeName = \"\";\n\n    friend class CMesaDRMResource;\n    friend class CMesaDRMBufferResource;\n};\n\nnamespace PROTO {\n    inline UP<CMesaDRMProtocol> mesaDRM;\n};\n"
  },
  {
    "path": "src/protocols/OutputManagement.cpp",
    "content": "#include \"OutputManagement.hpp\"\n#include <algorithm>\n#include \"../Compositor.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../event/EventBus.hpp\"\n\nusing namespace Aquamarine;\n\nCOutputManager::COutputManager(SP<CZwlrOutputManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    LOGM(Log::DEBUG, \"New OutputManager registered\");\n\n    m_resource->setOnDestroy([this](CZwlrOutputManagerV1* r) { PROTO::outputManagement->destroyResource(this); });\n\n    m_resource->setStop([this](CZwlrOutputManagerV1* r) { m_stopped = true; });\n\n    m_resource->setCreateConfiguration([this](CZwlrOutputManagerV1* r, uint32_t id, uint32_t serial) {\n        LOGM(Log::DEBUG, \"Creating new configuration\");\n\n        const auto RESOURCE = PROTO::outputManagement->m_configurations.emplace_back(\n            makeShared<COutputConfiguration>(makeShared<CZwlrOutputConfigurationV1>(m_resource->client(), m_resource->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            m_resource->noMemory();\n            PROTO::outputManagement->m_configurations.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n\n    // send all heads at start\n    for (auto const& m : g_pCompositor->m_realMonitors) {\n        if (m == g_pCompositor->m_unsafeOutput)\n            continue;\n\n        LOGM(Log::DEBUG, \" | sending output head for {}\", m->m_name);\n\n        makeAndSendNewHead(m);\n    }\n\n    sendDone();\n}\n\nbool COutputManager::good() {\n    return m_resource->resource();\n}\n\nvoid COutputManager::makeAndSendNewHead(PHLMONITOR pMonitor) {\n    if UNLIKELY (m_stopped)\n        return;\n\n    const auto RESOURCE =\n        PROTO::outputManagement->m_heads.emplace_back(makeShared<COutputHead>(makeShared<CZwlrOutputHeadV1>(m_resource->client(), m_resource->version(), 0), pMonitor));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        m_resource->noMemory();\n        PROTO::outputManagement->m_heads.pop_back();\n        return;\n    }\n\n    m_heads.emplace_back(RESOURCE);\n\n    m_resource->sendHead(RESOURCE->m_resource.get());\n    RESOURCE->sendAllData();\n}\n\nvoid COutputManager::ensureMonitorSent(PHLMONITOR pMonitor) {\n    if (pMonitor == g_pCompositor->m_unsafeOutput)\n        return;\n\n    for (auto const& hw : m_heads) {\n        auto h = hw.lock();\n\n        if (!h)\n            continue;\n\n        if (h->m_monitor == pMonitor)\n            return;\n    }\n\n    makeAndSendNewHead(pMonitor);\n\n    sendDone();\n}\n\nvoid COutputManager::sendDone() {\n    m_resource->sendDone(wl_display_next_serial(g_pCompositor->m_wlDisplay));\n}\n\nCOutputHead::COutputHead(SP<CZwlrOutputHeadV1> resource_, PHLMONITOR pMonitor_) : m_resource(resource_), m_monitor(pMonitor_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](CZwlrOutputHeadV1* r) { PROTO::outputManagement->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrOutputHeadV1* r) { PROTO::outputManagement->destroyResource(this); });\n\n    m_listeners.monitorDestroy = m_monitor->m_events.destroy.listen([this] {\n        m_resource->sendFinished();\n\n        for (auto const& mw : m_modes) {\n            auto m = mw.lock();\n\n            if (!m)\n                continue;\n\n            m->m_resource->sendFinished();\n        }\n\n        m_monitor.reset();\n        for (auto const& m : PROTO::outputManagement->m_managers) {\n            m->sendDone();\n        }\n    });\n\n    m_listeners.monitorModeChange = m_monitor->m_events.modeChanged.listen([this] { updateMode(); });\n}\n\nbool COutputHead::good() {\n    return m_resource->resource();\n}\n\nvoid COutputHead::sendAllData() {\n    const auto VERSION = m_resource->version();\n\n    m_resource->sendName(m_monitor->m_name.c_str());\n    m_resource->sendDescription(m_monitor->m_description.c_str());\n    if (m_monitor->m_output->physicalSize.x > 0 && m_monitor->m_output->physicalSize.y > 0)\n        m_resource->sendPhysicalSize(m_monitor->m_output->physicalSize.x, m_monitor->m_output->physicalSize.y);\n    m_resource->sendEnabled(m_monitor->m_enabled);\n\n    if (m_monitor->m_enabled) {\n        m_resource->sendPosition(m_monitor->m_position.x, m_monitor->m_position.y);\n        m_resource->sendTransform(m_monitor->m_transform);\n        m_resource->sendScale(wl_fixed_from_double(m_monitor->m_scale));\n    }\n\n    if (!m_monitor->m_output->make.empty() && VERSION >= 2)\n        m_resource->sendMake(m_monitor->m_output->make.c_str());\n    if (!m_monitor->m_output->model.empty() && VERSION >= 2)\n        m_resource->sendModel(m_monitor->m_output->model.c_str());\n    if (!m_monitor->m_output->serial.empty() && VERSION >= 2)\n        m_resource->sendSerialNumber(m_monitor->m_output->serial.c_str());\n\n    if (VERSION >= 4)\n        m_resource->sendAdaptiveSync(m_monitor->m_vrrActive ? ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED : ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED);\n\n    // send all available modes\n\n    if (m_modes.empty()) {\n        if (!m_monitor->m_output->modes.empty()) {\n            for (auto const& m : m_monitor->m_output->modes) {\n                makeAndSendNewMode(m);\n            }\n        } else if (m_monitor->m_output->state->state().customMode) {\n            makeAndSendNewMode(m_monitor->m_output->state->state().customMode);\n        } else\n            makeAndSendNewMode(nullptr);\n    }\n\n    // send current mode\n    if (m_monitor->m_enabled) {\n        for (auto const& mw : m_modes) {\n            auto m = mw.lock();\n\n            if (!m)\n                continue;\n\n            if (m->m_mode == m_monitor->m_output->state->state().mode) {\n                if (m->m_mode)\n                    LOGM(Log::DEBUG, \"  | sending current mode for {}: {}x{}@{}\", m_monitor->m_name, m->m_mode->pixelSize.x, m->m_mode->pixelSize.y, m->m_mode->refreshRate);\n                else\n                    LOGM(Log::DEBUG, \"  | sending current mode for {}: null (fake)\", m_monitor->m_name);\n                m_resource->sendCurrentMode(m->m_resource.get());\n                break;\n            }\n        }\n    }\n}\n\nvoid COutputHead::updateMode() {\n    m_resource->sendEnabled(m_monitor->m_enabled);\n\n    if (m_monitor->m_enabled) {\n        m_resource->sendPosition(m_monitor->m_position.x, m_monitor->m_position.y);\n        m_resource->sendTransform(m_monitor->m_transform);\n        m_resource->sendScale(wl_fixed_from_double(m_monitor->m_scale));\n    }\n\n    if (m_resource->version() >= 4)\n        m_resource->sendAdaptiveSync(m_monitor->m_vrrActive ? ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED : ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED);\n\n    if (m_monitor->m_enabled) {\n        for (auto const& mw : m_modes) {\n            auto m = mw.lock();\n\n            if (!m)\n                continue;\n\n            if (m->m_mode == m_monitor->m_currentMode) {\n                if (m->m_mode)\n                    LOGM(Log::DEBUG, \"  | sending current mode for {}: {}x{}@{}\", m_monitor->m_name, m->m_mode->pixelSize.x, m->m_mode->pixelSize.y, m->m_mode->refreshRate);\n                else\n                    LOGM(Log::DEBUG, \"  | sending current mode for {}: null (fake)\", m_monitor->m_name);\n                m_resource->sendCurrentMode(m->m_resource.get());\n                break;\n            }\n        }\n    }\n}\n\nvoid COutputHead::makeAndSendNewMode(SP<Aquamarine::SOutputMode> mode) {\n    const auto RESOURCE =\n        PROTO::outputManagement->m_modes.emplace_back(makeShared<COutputMode>(makeShared<CZwlrOutputModeV1>(m_resource->client(), m_resource->version(), 0), mode));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        m_resource->noMemory();\n        PROTO::outputManagement->m_modes.pop_back();\n        return;\n    }\n\n    m_modes.emplace_back(RESOURCE);\n    m_resource->sendMode(RESOURCE->m_resource.get());\n    RESOURCE->sendAllData();\n}\n\nPHLMONITOR COutputHead::monitor() {\n    return m_monitor.lock();\n}\n\nCOutputMode::COutputMode(SP<CZwlrOutputModeV1> resource_, SP<Aquamarine::SOutputMode> mode_) : m_resource(resource_), m_mode(mode_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](CZwlrOutputModeV1* r) { PROTO::outputManagement->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrOutputModeV1* r) { PROTO::outputManagement->destroyResource(this); });\n}\n\nvoid COutputMode::sendAllData() {\n    if (!m_mode)\n        return;\n\n    LOGM(Log::DEBUG, \"  | sending mode {}x{}@{}mHz, pref: {}\", m_mode->pixelSize.x, m_mode->pixelSize.y, m_mode->refreshRate, m_mode->preferred);\n\n    m_resource->sendSize(m_mode->pixelSize.x, m_mode->pixelSize.y);\n    if (m_mode->refreshRate > 0)\n        m_resource->sendRefresh(m_mode->refreshRate);\n    if (m_mode->preferred)\n        m_resource->sendPreferred();\n}\n\nbool COutputMode::good() {\n    return m_resource->resource();\n}\n\nSP<Aquamarine::SOutputMode> COutputMode::getMode() {\n    return m_mode.lock();\n}\n\nCOutputConfiguration::COutputConfiguration(SP<CZwlrOutputConfigurationV1> resource_, SP<COutputManager> owner_) : m_resource(resource_), m_owner(owner_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwlrOutputConfigurationV1* r) { PROTO::outputManagement->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrOutputConfigurationV1* r) { PROTO::outputManagement->destroyResource(this); });\n\n    m_resource->setEnableHead([this](CZwlrOutputConfigurationV1* r, uint32_t id, wl_resource* outputHead) {\n        const auto HEAD = PROTO::outputManagement->headFromResource(outputHead);\n\n        if (!HEAD) {\n            LOGM(Log::ERR, \"No head in setEnableHead??\");\n            return;\n        }\n\n        const auto PMONITOR = HEAD->monitor();\n\n        if (!PMONITOR) {\n            LOGM(Log::ERR, \"No monitor in setEnableHead??\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::outputManagement->m_configurationHeads.emplace_back(\n            makeShared<COutputConfigurationHead>(makeShared<CZwlrOutputConfigurationHeadV1>(m_resource->client(), m_resource->version(), id), PMONITOR));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            m_resource->noMemory();\n            PROTO::outputManagement->m_configurationHeads.pop_back();\n            return;\n        }\n\n        m_heads.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"enableHead on {}. For now, doing nothing. Waiting for apply().\", PMONITOR->m_name);\n    });\n\n    m_resource->setDisableHead([this](CZwlrOutputConfigurationV1* r, wl_resource* outputHead) {\n        const auto HEAD = PROTO::outputManagement->headFromResource(outputHead);\n\n        if (!HEAD) {\n            LOGM(Log::ERR, \"No head in setDisableHead??\");\n            return;\n        }\n\n        const auto PMONITOR = HEAD->monitor();\n\n        if (!PMONITOR) {\n            LOGM(Log::ERR, \"No monitor in setDisableHead??\");\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"disableHead on {}\", PMONITOR->m_name);\n\n        SWlrManagerSavedOutputState newState;\n        if (m_owner->m_monitorStates.contains(PMONITOR->m_name))\n            newState = m_owner->m_monitorStates.at(PMONITOR->m_name);\n\n        newState.enabled = false;\n\n        g_pConfigManager->m_wantsMonitorReload = true;\n\n        m_owner->m_monitorStates[PMONITOR->m_name] = newState;\n    });\n\n    m_resource->setTest([this](CZwlrOutputConfigurationV1* r) {\n        const auto SUCCESS = applyTestConfiguration(true);\n\n        if (SUCCESS)\n            m_resource->sendSucceeded();\n        else\n            m_resource->sendFailed();\n    });\n\n    m_resource->setApply([this](CZwlrOutputConfigurationV1* r) {\n        const auto SUCCESS = applyTestConfiguration(false);\n\n        if (SUCCESS)\n            PROTO::outputManagement->m_pendingConfigurationSuccessEvents.emplace_back(m_self);\n        else\n            m_resource->sendFailed();\n\n        m_owner->sendDone();\n    });\n}\n\nbool COutputConfiguration::good() {\n    return m_resource->resource();\n}\n\nbool COutputConfiguration::applyTestConfiguration(bool test) {\n    if (test) {\n        LOGM(Log::WARN, \"TODO: STUB: applyTestConfiguration for test not implemented, returning true.\");\n        return true;\n    }\n\n    LOGM(Log::DEBUG, \"Applying configuration\");\n\n    if (!m_owner) {\n        LOGM(Log::ERR, \"applyTestConfiguration: no owner?!\");\n        return false;\n    }\n\n    for (auto const& headw : m_heads) {\n        auto head = headw.lock();\n\n        if (!head)\n            continue;\n\n        const auto PMONITOR = head->m_monitor;\n\n        if (!PMONITOR)\n            continue;\n\n        LOGM(Log::DEBUG, \"Saving config for monitor {}\", PMONITOR->m_name);\n\n        SWlrManagerSavedOutputState newState;\n        if (m_owner->m_monitorStates.contains(PMONITOR->m_name))\n            newState = m_owner->m_monitorStates.at(PMONITOR->m_name);\n\n        newState.enabled = true;\n\n        if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE) {\n            newState.resolution = head->m_state.mode->getMode()->pixelSize;\n            newState.refresh    = head->m_state.mode->getMode()->refreshRate;\n            newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE;\n            LOGM(Log::DEBUG, \" > Mode: {:.0f}x{:.0f}@{}mHz\", newState.resolution.x, newState.resolution.y, newState.refresh);\n        } else if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) {\n            newState.resolution = head->m_state.customMode.size;\n            newState.refresh    = head->m_state.customMode.refresh;\n            newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE;\n            LOGM(Log::DEBUG, \" > Custom mode: {:.0f}x{:.0f}@{}mHz\", newState.resolution.x, newState.resolution.y, newState.refresh);\n        }\n\n        if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION) {\n            newState.position = head->m_state.position;\n            newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION;\n            LOGM(Log::DEBUG, \" > Position: {:.0f}, {:.0f}\", head->m_state.position.x, head->m_state.position.y);\n        }\n\n        if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) {\n            newState.adaptiveSync = head->m_state.adaptiveSync;\n            newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC;\n            LOGM(Log::DEBUG, \" > vrr: {}\", newState.adaptiveSync);\n        }\n\n        if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE) {\n            newState.scale = head->m_state.scale;\n            newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE;\n            LOGM(Log::DEBUG, \" > scale: {:.2f}\", newState.scale);\n        }\n\n        if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM) {\n            newState.transform = head->m_state.transform;\n            newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM;\n            LOGM(Log::DEBUG, \" > transform: {}\", (uint8_t)newState.transform);\n        }\n\n        // reset properties for next set.\n        head->m_state.committedProperties = 0;\n\n        g_pConfigManager->m_wantsMonitorReload = true;\n\n        m_owner->m_monitorStates[PMONITOR->m_name] = newState;\n    }\n\n    LOGM(Log::DEBUG, \"Saved configuration\");\n\n    return true;\n}\n\nCOutputConfigurationHead::COutputConfigurationHead(SP<CZwlrOutputConfigurationHeadV1> resource_, PHLMONITOR pMonitor_) : m_resource(resource_), m_monitor(pMonitor_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwlrOutputConfigurationHeadV1* r) { PROTO::outputManagement->destroyResource(this); });\n\n    m_resource->setSetMode([this](CZwlrOutputConfigurationHeadV1* r, wl_resource* outputMode) {\n        const auto MODE = PROTO::outputManagement->modeFromResource(outputMode);\n\n        if (!MODE || !MODE->getMode()) {\n            LOGM(Log::ERR, \"No mode in setMode??\");\n            return;\n        }\n\n        if (!m_monitor) {\n            LOGM(Log::ERR, \"setMode on inert resource\");\n            return;\n        }\n\n        if (m_state.committedProperties & OUTPUT_HEAD_COMMITTED_MODE) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, \"Property already set\");\n            return;\n        }\n\n        m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_MODE;\n        m_state.mode = MODE;\n\n        LOGM(Log::DEBUG, \" | configHead for {}: set mode to {}x{}@{}\", m_monitor->m_name, MODE->getMode()->pixelSize.x, MODE->getMode()->pixelSize.y, MODE->getMode()->refreshRate);\n    });\n\n    m_resource->setSetCustomMode([this](CZwlrOutputConfigurationHeadV1* r, int32_t w, int32_t h, int32_t refresh) {\n        if (!m_monitor) {\n            LOGM(Log::ERR, \"setCustomMode on inert resource\");\n            return;\n        }\n\n        if (m_state.committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, \"Property already set\");\n            return;\n        }\n\n        if (w <= 0 || h <= 0 || refresh < 0) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE, \"Invalid mode\");\n            return;\n        }\n\n        if (refresh == 0) {\n            LOGM(Log::DEBUG, \" | configHead for {}: refreshRate 0, using old refresh rate of {:.2f}Hz\", m_monitor->m_name, m_monitor->m_refreshRate);\n            refresh = std::round(m_monitor->m_refreshRate * 1000.F);\n        }\n\n        m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_CUSTOM_MODE;\n        m_state.customMode = {{w, h}, sc<uint32_t>(refresh)};\n\n        LOGM(Log::DEBUG, \" | configHead for {}: set custom mode to {}x{}@{}\", m_monitor->m_name, w, h, refresh);\n    });\n\n    m_resource->setSetPosition([this](CZwlrOutputConfigurationHeadV1* r, int32_t x, int32_t y) {\n        if (!m_monitor) {\n            LOGM(Log::ERR, \"setMode on inert resource\");\n            return;\n        }\n\n        if (m_state.committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, \"Property already set\");\n            return;\n        }\n\n        m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_POSITION;\n        m_state.position = {x, y};\n\n        LOGM(Log::DEBUG, \" | configHead for {}: set pos to {}, {}\", m_monitor->m_name, x, y);\n    });\n\n    m_resource->setSetTransform([this](CZwlrOutputConfigurationHeadV1* r, int32_t transform) {\n        if (!m_monitor) {\n            LOGM(Log::ERR, \"setMode on inert resource\");\n            return;\n        }\n\n        if (m_state.committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, \"Property already set\");\n            return;\n        }\n\n        if (transform < 0 || transform > 7) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM, \"Invalid transform\");\n            return;\n        }\n\n        m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_TRANSFORM;\n        m_state.transform = sc<wl_output_transform>(transform);\n\n        LOGM(Log::DEBUG, \" | configHead for {}: set transform to {}\", m_monitor->m_name, transform);\n    });\n\n    m_resource->setSetScale([this](CZwlrOutputConfigurationHeadV1* r, wl_fixed_t scale_) {\n        if (!m_monitor) {\n            LOGM(Log::ERR, \"setMode on inert resource\");\n            return;\n        }\n\n        if (m_state.committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, \"Property already set\");\n            return;\n        }\n\n        double scale = wl_fixed_to_double(scale_);\n\n        if (scale < 0.1 || scale > 10.0) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_SCALE, \"Invalid scale\");\n            return;\n        }\n\n        m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_SCALE;\n        m_state.scale = scale;\n\n        LOGM(Log::DEBUG, \" | configHead for {}: set scale to {:.2f}\", m_monitor->m_name, scale);\n    });\n\n    m_resource->setSetAdaptiveSync([this](CZwlrOutputConfigurationHeadV1* r, uint32_t as) {\n        if (!m_monitor) {\n            LOGM(Log::ERR, \"setMode on inert resource\");\n            return;\n        }\n\n        if (m_state.committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, \"Property already set\");\n            return;\n        }\n\n        if (as > 1) {\n            m_resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE, \"Invalid adaptive sync state\");\n            return;\n        }\n\n        m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC;\n        m_state.adaptiveSync = as;\n\n        LOGM(Log::DEBUG, \" | configHead for {}: set adaptiveSync to {}\", m_monitor->m_name, as);\n    });\n}\n\nbool COutputConfigurationHead::good() {\n    return m_resource->resource();\n}\n\nCOutputManagementProtocol::COutputManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] {\n        updateAllOutputs();\n        sendPendingSuccessEvents();\n    });\n}\n\nvoid COutputManagementProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<COutputManager>(makeShared<CZwlrOutputManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self = RESOURCE;\n}\n\nvoid COutputManagementProtocol::destroyResource(COutputManager* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid COutputManagementProtocol::destroyResource(COutputHead* resource) {\n    std::erase_if(m_heads, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid COutputManagementProtocol::destroyResource(COutputMode* resource) {\n    std::erase_if(m_modes, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid COutputManagementProtocol::destroyResource(COutputConfiguration* resource) {\n    std::erase_if(m_configurations, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid COutputManagementProtocol::destroyResource(COutputConfigurationHead* resource) {\n    std::erase_if(m_configurationHeads, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid COutputManagementProtocol::updateAllOutputs() {\n    for (auto const& m : g_pCompositor->m_realMonitors) {\n        for (auto const& mgr : m_managers) {\n            mgr->ensureMonitorSent(m);\n        }\n    }\n}\n\nSP<COutputHead> COutputManagementProtocol::headFromResource(wl_resource* r) {\n    for (auto const& h : m_heads) {\n        if (h->m_resource->resource() == r)\n            return h;\n    }\n\n    return nullptr;\n}\n\nSP<COutputMode> COutputManagementProtocol::modeFromResource(wl_resource* r) {\n    for (auto const& h : m_modes) {\n        if (h->m_resource->resource() == r)\n            return h;\n    }\n\n    return nullptr;\n}\n\nSP<SWlrManagerSavedOutputState> COutputManagementProtocol::getOutputStateFor(PHLMONITOR pMonitor) {\n    for (auto const& m : m_managers) {\n        if (!m->m_monitorStates.contains(pMonitor->m_name))\n            continue;\n\n        return makeShared<SWlrManagerSavedOutputState>(m->m_monitorStates.at(pMonitor->m_name));\n    }\n\n    return nullptr;\n}\n\nvoid COutputManagementProtocol::sendPendingSuccessEvents() {\n    if (m_pendingConfigurationSuccessEvents.empty())\n        return;\n\n    LOGM(Log::DEBUG, \"Sending {} pending configuration success events\", m_pendingConfigurationSuccessEvents.size());\n\n    for (auto const& config : m_pendingConfigurationSuccessEvents) {\n        if (!config)\n            continue;\n\n        config->m_resource->sendSucceeded();\n    }\n\n    m_pendingConfigurationSuccessEvents.clear();\n}\n"
  },
  {
    "path": "src/protocols/OutputManagement.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-output-management-unstable-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include <aquamarine/output/Output.hpp>\n\nclass CMonitor;\n\nclass COutputHead;\nclass COutputMode;\n\nstruct SMonitorRule;\n\nenum eWlrOutputCommittedProperties : uint32_t {\n    OUTPUT_HEAD_COMMITTED_MODE          = (1 << 0),\n    OUTPUT_HEAD_COMMITTED_CUSTOM_MODE   = (1 << 1),\n    OUTPUT_HEAD_COMMITTED_POSITION      = (1 << 2),\n    OUTPUT_HEAD_COMMITTED_TRANSFORM     = (1 << 3),\n    OUTPUT_HEAD_COMMITTED_SCALE         = (1 << 4),\n    OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC = (1 << 5),\n};\n\nstruct SWlrManagerOutputState {\n    uint32_t        committedProperties = 0;\n\n    WP<COutputMode> mode;\n    struct {\n        Vector2D size;\n        uint32_t refresh = 0;\n    } customMode;\n    Vector2D            position;\n    wl_output_transform transform    = WL_OUTPUT_TRANSFORM_NORMAL;\n    float               scale        = 1.F;\n    bool                adaptiveSync = false;\n    bool                enabled      = true;\n};\n\nstruct SWlrManagerSavedOutputState {\n    uint32_t            committedProperties = 0;\n    Vector2D            resolution;\n    uint32_t            refresh = 0;\n    Vector2D            position;\n    wl_output_transform transform    = WL_OUTPUT_TRANSFORM_NORMAL;\n    float               scale        = 1.F;\n    bool                adaptiveSync = false;\n    bool                enabled      = true;\n};\n\nclass COutputManager {\n  public:\n    COutputManager(SP<CZwlrOutputManagerV1> resource_);\n\n    bool good();\n    void ensureMonitorSent(PHLMONITOR pMonitor);\n    void sendDone();\n\n    // holds the states for this manager.\n    std::unordered_map<std::string, SWlrManagerSavedOutputState> m_monitorStates;\n\n  private:\n    SP<CZwlrOutputManagerV1>     m_resource;\n    bool                         m_stopped = false;\n\n    WP<COutputManager>           m_self;\n\n    std::vector<WP<COutputHead>> m_heads;\n\n    void                         makeAndSendNewHead(PHLMONITOR pMonitor);\n    friend class COutputManagementProtocol;\n};\n\nclass COutputMode {\n  public:\n    COutputMode(SP<CZwlrOutputModeV1> resource_, SP<Aquamarine::SOutputMode> mode_);\n\n    bool                        good();\n    SP<Aquamarine::SOutputMode> getMode();\n    void                        sendAllData();\n\n  private:\n    SP<CZwlrOutputModeV1>       m_resource;\n    WP<Aquamarine::SOutputMode> m_mode;\n\n    friend class COutputHead;\n    friend class COutputManagementProtocol;\n};\n\nclass COutputHead {\n  public:\n    COutputHead(SP<CZwlrOutputHeadV1> resource_, PHLMONITOR pMonitor_);\n\n    bool       good();\n    void       sendAllData(); // this has to be separate as we need to send the head first, then set the data\n    void       updateMode();\n    PHLMONITOR monitor();\n\n  private:\n    SP<CZwlrOutputHeadV1>        m_resource;\n    PHLMONITORREF                m_monitor;\n\n    void                         makeAndSendNewMode(SP<Aquamarine::SOutputMode> mode);\n    void                         sendCurrentMode();\n\n    std::vector<WP<COutputMode>> m_modes;\n\n    struct {\n        CHyprSignalListener monitorDestroy;\n        CHyprSignalListener monitorModeChange;\n    } m_listeners;\n\n    friend class COutputManager;\n    friend class COutputManagementProtocol;\n};\n\nclass COutputConfigurationHead {\n  public:\n    COutputConfigurationHead(SP<CZwlrOutputConfigurationHeadV1> resource_, PHLMONITOR pMonitor_);\n\n    bool                   good();\n\n    SWlrManagerOutputState m_state;\n\n  private:\n    SP<CZwlrOutputConfigurationHeadV1> m_resource;\n    PHLMONITORREF                      m_monitor;\n\n    friend class COutputConfiguration;\n};\n\nclass COutputConfiguration {\n  public:\n    COutputConfiguration(SP<CZwlrOutputConfigurationV1> resource_, SP<COutputManager> owner_);\n\n    bool good();\n\n  private:\n    SP<CZwlrOutputConfigurationV1>            m_resource;\n    std::vector<WP<COutputConfigurationHead>> m_heads;\n    WP<COutputManager>                        m_owner;\n    WP<COutputConfiguration>                  m_self;\n\n    bool                                      applyTestConfiguration(bool test);\n\n    friend class COutputManagementProtocol;\n    friend class COutputManager;\n};\n\nclass COutputManagementProtocol : public IWaylandProtocol {\n  public:\n    COutputManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    // doesn't have to return one\n    SP<SWlrManagerSavedOutputState> getOutputStateFor(PHLMONITOR pMonitor);\n\n    void                            sendPendingSuccessEvents();\n\n  private:\n    void destroyResource(COutputManager* resource);\n    void destroyResource(COutputHead* resource);\n    void destroyResource(COutputMode* resource);\n    void destroyResource(COutputConfiguration* resource);\n    void destroyResource(COutputConfigurationHead* resource);\n\n    void updateAllOutputs();\n\n    //\n    std::vector<SP<COutputManager>>           m_managers;\n    std::vector<SP<COutputHead>>              m_heads;\n    std::vector<SP<COutputMode>>              m_modes;\n    std::vector<SP<COutputConfiguration>>     m_configurations;\n    std::vector<SP<COutputConfigurationHead>> m_configurationHeads;\n    std::vector<WP<COutputConfiguration>>     m_pendingConfigurationSuccessEvents;\n\n    SP<COutputHead>                           headFromResource(wl_resource* r);\n    SP<COutputMode>                           modeFromResource(wl_resource* r);\n\n    friend class COutputManager;\n    friend class COutputHead;\n    friend class COutputMode;\n    friend class COutputConfiguration;\n    friend class COutputConfigurationHead;\n};\n\nnamespace PROTO {\n    inline UP<COutputManagementProtocol> outputManagement;\n};\n"
  },
  {
    "path": "src/protocols/OutputPower.cpp",
    "content": "#include \"OutputPower.hpp\"\n#include \"core/Output.hpp\"\n#include \"../helpers/Monitor.hpp\"\n\nCOutputPower::COutputPower(SP<CZwlrOutputPowerV1> resource_, PHLMONITOR pMonitor_) : m_resource(resource_), m_monitor(pMonitor_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CZwlrOutputPowerV1* r) { PROTO::outputPower->destroyOutputPower(this); });\n    m_resource->setOnDestroy([this](CZwlrOutputPowerV1* r) { PROTO::outputPower->destroyOutputPower(this); });\n\n    m_resource->setSetMode([this](CZwlrOutputPowerV1* r, zwlrOutputPowerV1Mode mode) {\n        if (!m_monitor)\n            return;\n\n        m_monitor->setDPMS(mode == ZWLR_OUTPUT_POWER_V1_MODE_ON);\n    });\n\n    m_resource->sendMode(m_monitor->m_dpmsStatus ? ZWLR_OUTPUT_POWER_V1_MODE_ON : ZWLR_OUTPUT_POWER_V1_MODE_OFF);\n\n    m_listeners.monitorDestroy = m_monitor->m_events.destroy.listen([this] {\n        m_monitor.reset();\n        m_resource->sendFailed();\n    });\n\n    m_listeners.monitorDpms =\n        m_monitor->m_events.dpmsChanged.listen([this] { m_resource->sendMode(m_monitor->m_dpmsStatus ? ZWLR_OUTPUT_POWER_V1_MODE_ON : ZWLR_OUTPUT_POWER_V1_MODE_OFF); });\n    m_listeners.monitorState =\n        m_monitor->m_events.modeChanged.listen([this] { m_resource->sendMode(m_monitor->m_dpmsStatus ? ZWLR_OUTPUT_POWER_V1_MODE_ON : ZWLR_OUTPUT_POWER_V1_MODE_OFF); });\n}\n\nbool COutputPower::good() {\n    return m_resource->resource();\n}\n\nCOutputPowerProtocol::COutputPowerProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid COutputPowerProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwlrOutputPowerManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwlrOutputPowerManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwlrOutputPowerManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetOutputPower([this](CZwlrOutputPowerManagerV1* hiThereFriend, uint32_t id, wl_resource* output) { this->onGetOutputPower(hiThereFriend, id, output); });\n}\n\nvoid COutputPowerProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid COutputPowerProtocol::destroyOutputPower(COutputPower* power) {\n    std::erase_if(m_outputPowers, [&](const auto& other) { return other.get() == power; });\n}\n\nvoid COutputPowerProtocol::onGetOutputPower(CZwlrOutputPowerManagerV1* pMgr, uint32_t id, wl_resource* output) {\n\n    const auto OUTPUT = CWLOutputResource::fromResource(output);\n\n    if UNLIKELY (!OUTPUT) {\n        pMgr->error(0, \"Invalid output resource\");\n        return;\n    }\n\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_outputPowers.emplace_back(makeUnique<COutputPower>(makeShared<CZwlrOutputPowerV1>(CLIENT, pMgr->version(), id), OUTPUT->m_monitor.lock())).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_outputPowers.pop_back();\n        return;\n    }\n}\n"
  },
  {
    "path": "src/protocols/OutputPower.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-output-power-management-unstable-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CMonitor;\n\nclass COutputPower {\n  public:\n    COutputPower(SP<CZwlrOutputPowerV1> resource_, PHLMONITOR pMonitor);\n\n    bool good();\n\n  private:\n    SP<CZwlrOutputPowerV1> m_resource;\n\n    PHLMONITORREF          m_monitor;\n\n    struct {\n        CHyprSignalListener monitorDestroy;\n        CHyprSignalListener monitorState;\n        CHyprSignalListener monitorDpms;\n    } m_listeners;\n};\n\nclass COutputPowerProtocol : public IWaylandProtocol {\n  public:\n    COutputPowerProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyOutputPower(COutputPower* pointer);\n    void onGetOutputPower(CZwlrOutputPowerManagerV1* pMgr, uint32_t id, wl_resource* output);\n\n    //\n    std::vector<UP<CZwlrOutputPowerManagerV1>> m_managers;\n    std::vector<UP<COutputPower>>              m_outputPowers;\n\n    friend class COutputPower;\n};\n\nnamespace PROTO {\n    inline UP<COutputPowerProtocol> outputPower;\n};"
  },
  {
    "path": "src/protocols/PointerConstraints.cpp",
    "content": "#include \"PointerConstraints.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../helpers/Monitor.hpp\"\n\nCPointerConstraint::CPointerConstraint(SP<CZwpLockedPointerV1> resource_, SP<CWLSurfaceResource> surf, wl_resource* region_, zwpPointerConstraintsV1Lifetime lifetime_) :\n    m_resourceLocked(resource_), m_locked(true), m_lifetime(lifetime_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    resource_->setOnDestroy([this](CZwpLockedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); });\n    resource_->setDestroy([this](CZwpLockedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); });\n\n    m_hlSurface = Desktop::View::CWLSurface::fromResource(surf);\n\n    if (!m_hlSurface)\n        return;\n\n    if (region_)\n        m_region.set(CWLRegionResource::fromResource(region_)->m_region);\n\n    resource_->setSetRegion([this](CZwpLockedPointerV1* p, wl_resource* region) { onSetRegion(region); });\n    resource_->setSetCursorPositionHint([this](CZwpLockedPointerV1* p, wl_fixed_t x, wl_fixed_t y) {\n        static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n        if (!m_hlSurface)\n            return;\n\n        m_hintSet = true;\n\n        float      scale   = 1.f;\n        const auto PWINDOW = Desktop::View::CWindow::fromView(m_hlSurface->view());\n        if (PWINDOW) {\n            const auto ISXWL = PWINDOW->m_isX11;\n            scale            = ISXWL && *PXWLFORCESCALEZERO ? PWINDOW->m_X11SurfaceScaledBy : 1.f;\n        }\n\n        m_positionHint = {wl_fixed_to_double(x) / scale, wl_fixed_to_double(y) / scale};\n        g_pInputManager->simulateMouseMovement();\n    });\n\n    sharedConstructions();\n}\n\nCPointerConstraint::CPointerConstraint(SP<CZwpConfinedPointerV1> resource_, SP<CWLSurfaceResource> surf, wl_resource* region_, zwpPointerConstraintsV1Lifetime lifetime_) :\n    m_resourceConfined(resource_), m_lifetime(lifetime_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    resource_->setOnDestroy([this](CZwpConfinedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); });\n    resource_->setDestroy([this](CZwpConfinedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); });\n\n    m_hlSurface = Desktop::View::CWLSurface::fromResource(surf);\n\n    if (!m_hlSurface)\n        return;\n\n    if (region_)\n        m_region.set(CWLRegionResource::fromResource(region_)->m_region);\n\n    resource_->setSetRegion([this](CZwpConfinedPointerV1* p, wl_resource* region) { onSetRegion(region); });\n\n    sharedConstructions();\n}\n\nCPointerConstraint::~CPointerConstraint() {\n    std::erase_if(g_pInputManager->m_constraints, [this](const auto& c) {\n        const auto SHP = c.lock();\n        return !SHP || SHP.get() == this;\n    });\n\n    if (m_hlSurface)\n        m_hlSurface->m_constraint.reset();\n}\n\nvoid CPointerConstraint::sharedConstructions() {\n    if (m_hlSurface) {\n        m_listeners.destroySurface = m_hlSurface->m_events.destroy.listen([this] {\n            m_hlSurface.reset();\n            if (m_active)\n                deactivate();\n\n            std::erase_if(g_pInputManager->m_constraints, [this](const auto& c) {\n                const auto SHP = c.lock();\n                return !SHP || SHP.get() == this;\n            });\n        });\n    }\n\n    m_cursorPosOnActivate = g_pInputManager->getMouseCoordsInternal();\n}\n\nbool CPointerConstraint::good() {\n    return m_locked ? m_resourceLocked->resource() : m_resourceConfined->resource();\n}\n\nvoid CPointerConstraint::deactivate() {\n    if (!m_active)\n        return;\n\n    if (m_locked)\n        m_resourceLocked->sendUnlocked();\n    else\n        m_resourceConfined->sendUnconfined();\n\n    m_active = false;\n\n    if (m_lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) {\n        m_dead = true;\n        // remove from inputmgr\n        std::erase_if(g_pInputManager->m_constraints, [this](const auto& c) {\n            const auto SHP = c.lock();\n            return !SHP || SHP.get() == this;\n        });\n    }\n}\n\nvoid CPointerConstraint::activate() {\n    if (m_dead || m_active)\n        return;\n\n    // TODO: hack, probably not a super duper great idea\n    if (g_pSeatManager->m_state.pointerFocus != m_hlSurface->resource()) {\n        const auto SURFBOX = m_hlSurface->getSurfaceBoxGlobal();\n        const auto LOCAL   = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{};\n        g_pSeatManager->setPointerFocus(m_hlSurface->resource(), LOCAL);\n    }\n\n    if (m_locked)\n        m_resourceLocked->sendLocked();\n    else\n        m_resourceConfined->sendConfined();\n\n    m_active = true;\n\n    g_pInputManager->simulateMouseMovement();\n}\n\nbool CPointerConstraint::isActive() {\n    return m_active;\n}\n\nvoid CPointerConstraint::onSetRegion(wl_resource* wlRegion) {\n    if (!wlRegion) {\n        m_region.clear();\n        return;\n    }\n\n    const auto REGION = m_region.set(CWLRegionResource::fromResource(wlRegion)->m_region);\n\n    m_region.set(REGION);\n    m_positionHint = m_region.closestPoint(m_positionHint);\n    g_pInputManager->simulateMouseMovement(); // to warp the cursor if anything's amiss\n}\n\nSP<Desktop::View::CWLSurface> CPointerConstraint::owner() {\n    return m_hlSurface.lock();\n}\n\nCRegion CPointerConstraint::logicConstraintRegion() {\n    CRegion    rg      = m_region;\n    const auto SURFBOX = m_hlSurface->getSurfaceBoxGlobal();\n\n    // if region wasn't set in pointer-constraints request take surface region\n    if (rg.empty() && SURFBOX.has_value()) {\n        rg.set(SURFBOX.value());\n        return rg;\n    }\n\n    const auto CONSTRAINTPOS = SURFBOX.has_value() ? SURFBOX->pos() : Vector2D{};\n    rg.translate(CONSTRAINTPOS);\n    return rg;\n}\n\nbool CPointerConstraint::isLocked() {\n    return m_locked;\n}\n\nVector2D CPointerConstraint::logicPositionHint() {\n    if UNLIKELY (!m_hlSurface)\n        return {};\n\n    const auto SURFBOX       = m_hlSurface->getSurfaceBoxGlobal();\n    const auto CONSTRAINTPOS = SURFBOX.has_value() ? SURFBOX->pos() : Vector2D{};\n\n    return m_hintSet ? CONSTRAINTPOS + m_positionHint : m_cursorPosOnActivate;\n}\n\nCPointerConstraintsProtocol::CPointerConstraintsProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CPointerConstraintsProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpPointerConstraintsV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpPointerConstraintsV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpPointerConstraintsV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setConfinePointer([this](CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region,\n                                       zwpPointerConstraintsV1Lifetime lifetime) { this->onConfinePointer(pMgr, id, surface, pointer, region, lifetime); });\n    RESOURCE->setLockPointer([this](CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region,\n                                    zwpPointerConstraintsV1Lifetime lifetime) { this->onLockPointer(pMgr, id, surface, pointer, region, lifetime); });\n}\n\nvoid CPointerConstraintsProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CPointerConstraintsProtocol::destroyPointerConstraint(CPointerConstraint* hyprlandEgg) {\n    std::erase_if(m_constraints, [&](const auto& other) { return other.get() == hyprlandEgg; });\n}\n\nvoid CPointerConstraintsProtocol::onNewConstraint(SP<CPointerConstraint> constraint, CZwpPointerConstraintsV1* pMgr) {\n    if UNLIKELY (!constraint->good()) {\n        LOGM(Log::ERR, \"Couldn't create constraint??\");\n        pMgr->noMemory();\n        m_constraints.pop_back();\n        return;\n    }\n\n    if UNLIKELY (!constraint->owner()) {\n        LOGM(Log::ERR, \"New constraint has no CWLSurface owner??\");\n        return;\n    }\n\n    const auto OWNER = constraint->owner();\n\n    const auto DUPES = std::ranges::count_if(m_constraints, [OWNER](const auto& c) { return c->owner() == OWNER; });\n\n    if UNLIKELY (DUPES > 1) {\n        LOGM(Log::ERR, \"Constraint for surface duped\");\n        pMgr->error(ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, \"Surface already confined\");\n        m_constraints.pop_back();\n        return;\n    }\n\n    OWNER->appendConstraint(constraint);\n\n    g_pInputManager->m_constraints.emplace_back(constraint);\n\n    if (Desktop::focusState()->surface() == OWNER->resource())\n        constraint->activate();\n}\n\nvoid CPointerConstraintsProtocol::onLockPointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region,\n                                                zwpPointerConstraintsV1Lifetime lifetime) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_constraints.emplace_back(\n        makeShared<CPointerConstraint>(makeShared<CZwpLockedPointerV1>(CLIENT, pMgr->version(), id), CWLSurfaceResource::fromResource(surface), region, lifetime));\n\n    onNewConstraint(RESOURCE, pMgr);\n}\n\nvoid CPointerConstraintsProtocol::onConfinePointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region,\n                                                   zwpPointerConstraintsV1Lifetime lifetime) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_constraints.emplace_back(\n        makeShared<CPointerConstraint>(makeShared<CZwpConfinedPointerV1>(CLIENT, pMgr->version(), id), CWLSurfaceResource::fromResource(surface), region, lifetime));\n\n    onNewConstraint(RESOURCE, pMgr);\n}\n"
  },
  {
    "path": "src/protocols/PointerConstraints.hpp",
    "content": "#pragma once\n\n#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"pointer-constraints-unstable-v1.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CPointerConstraint {\n  public:\n    CPointerConstraint(SP<CZwpLockedPointerV1> resource_, SP<CWLSurfaceResource> surf, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime_);\n    CPointerConstraint(SP<CZwpConfinedPointerV1> resource_, SP<CWLSurfaceResource> surf, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime_);\n    ~CPointerConstraint();\n\n    bool                          good();\n\n    void                          deactivate();\n    void                          activate();\n    bool                          isActive();\n\n    SP<Desktop::View::CWLSurface> owner();\n\n    CRegion                       logicConstraintRegion();\n    bool                          isLocked();\n    Vector2D                      logicPositionHint();\n\n  private:\n    SP<CZwpLockedPointerV1>         m_resourceLocked;\n    SP<CZwpConfinedPointerV1>       m_resourceConfined;\n\n    WP<Desktop::View::CWLSurface>   m_hlSurface;\n\n    CRegion                         m_region;\n    bool                            m_hintSet             = false;\n    Vector2D                        m_positionHint        = {-1, -1};\n    Vector2D                        m_cursorPosOnActivate = {-1, -1};\n    bool                            m_active              = false;\n    bool                            m_locked              = false;\n    bool                            m_dead                = false;\n    zwpPointerConstraintsV1Lifetime m_lifetime            = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT;\n\n    void                            sharedConstructions();\n    void                            onSetRegion(wl_resource* region);\n\n    struct {\n        CHyprSignalListener destroySurface;\n    } m_listeners;\n};\n\nclass CPointerConstraintsProtocol : public IWaylandProtocol {\n  public:\n    CPointerConstraintsProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyPointerConstraint(CPointerConstraint* constraint);\n    void onLockPointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime);\n    void onConfinePointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime);\n    void onNewConstraint(SP<CPointerConstraint> constraint, CZwpPointerConstraintsV1* pMgr);\n\n    //\n    std::vector<UP<CZwpPointerConstraintsV1>> m_managers;\n    std::vector<SP<CPointerConstraint>>       m_constraints;\n\n    friend class CPointerConstraint;\n};\n\nnamespace PROTO {\n    inline UP<CPointerConstraintsProtocol> constraints;\n};"
  },
  {
    "path": "src/protocols/PointerGestures.cpp",
    "content": "#include \"PointerGestures.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"core/Seat.hpp\"\n#include \"core/Compositor.hpp\"\n\nCPointerGestureSwipe::CPointerGestureSwipe(SP<CZwpPointerGestureSwipeV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpPointerGestureSwipeV1* p) { PROTO::pointerGestures->onGestureDestroy(this); });\n    m_resource->setDestroy([this](CZwpPointerGestureSwipeV1* p) { PROTO::pointerGestures->onGestureDestroy(this); });\n}\n\nbool CPointerGestureSwipe::good() {\n    return m_resource->resource();\n}\n\nCPointerGestureHold::CPointerGestureHold(SP<CZwpPointerGestureHoldV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpPointerGestureHoldV1* p) { PROTO::pointerGestures->onGestureDestroy(this); });\n    m_resource->setDestroy([this](CZwpPointerGestureHoldV1* p) { PROTO::pointerGestures->onGestureDestroy(this); });\n}\n\nbool CPointerGestureHold::good() {\n    return m_resource->resource();\n}\n\nCPointerGesturePinch::CPointerGesturePinch(SP<CZwpPointerGesturePinchV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpPointerGesturePinchV1* p) { PROTO::pointerGestures->onGestureDestroy(this); });\n    m_resource->setDestroy([this](CZwpPointerGesturePinchV1* p) { PROTO::pointerGestures->onGestureDestroy(this); });\n}\n\nbool CPointerGesturePinch::good() {\n    return m_resource->resource();\n}\n\nCPointerGesturesProtocol::CPointerGesturesProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CPointerGesturesProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpPointerGesturesV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpPointerGesturesV1* p) { this->onManagerResourceDestroy(p->resource()); });\n    RESOURCE->setRelease([this](CZwpPointerGesturesV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n\n    RESOURCE->setGetHoldGesture([this](CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer) { this->onGetHoldGesture(pMgr, id, pointer); });\n    RESOURCE->setGetPinchGesture([this](CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer) { this->onGetPinchGesture(pMgr, id, pointer); });\n    RESOURCE->setGetSwipeGesture([this](CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer) { this->onGetSwipeGesture(pMgr, id, pointer); });\n}\n\nvoid CPointerGesturesProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CPointerGesturesProtocol::onGestureDestroy(CPointerGestureSwipe* gesture) {\n    std::erase_if(m_swipes, [&](const auto& other) { return other.get() == gesture; });\n}\n\nvoid CPointerGesturesProtocol::onGestureDestroy(CPointerGesturePinch* gesture) {\n    std::erase_if(m_pinches, [&](const auto& other) { return other.get() == gesture; });\n}\n\nvoid CPointerGesturesProtocol::onGestureDestroy(CPointerGestureHold* gesture) {\n    std::erase_if(m_holds, [&](const auto& other) { return other.get() == gesture; });\n}\n\nvoid CPointerGesturesProtocol::onGetPinchGesture(CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_pinches.emplace_back(makeUnique<CPointerGesturePinch>(makeShared<CZwpPointerGesturePinchV1>(CLIENT, pMgr->version(), id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        LOGM(Log::ERR, \"Couldn't create gesture\");\n        return;\n    }\n}\n\nvoid CPointerGesturesProtocol::onGetSwipeGesture(CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_swipes.emplace_back(makeUnique<CPointerGestureSwipe>(makeShared<CZwpPointerGestureSwipeV1>(CLIENT, pMgr->version(), id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        LOGM(Log::ERR, \"Couldn't create gesture\");\n        return;\n    }\n}\n\nvoid CPointerGesturesProtocol::onGetHoldGesture(CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_holds.emplace_back(makeUnique<CPointerGestureHold>(makeShared<CZwpPointerGestureHoldV1>(CLIENT, pMgr->version(), id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        LOGM(Log::ERR, \"Couldn't create gesture\");\n        return;\n    }\n}\n\nvoid CPointerGesturesProtocol::swipeBegin(uint32_t timeMs, uint32_t fingers) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->m_state.pointerFocusResource.lock());\n\n    for (auto const& sw : m_swipes) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendBegin(SERIAL, timeMs, g_pSeatManager->m_state.pointerFocus->getResource()->resource(), fingers);\n    }\n}\n\nvoid CPointerGesturesProtocol::swipeUpdate(uint32_t timeMs, const Vector2D& delta) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    for (auto const& sw : m_swipes) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendUpdate(timeMs, wl_fixed_from_double(delta.x), wl_fixed_from_double(delta.y));\n    }\n}\n\nvoid CPointerGesturesProtocol::swipeEnd(uint32_t timeMs, bool cancelled) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->m_state.pointerFocusResource.lock());\n\n    for (auto const& sw : m_swipes) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendEnd(SERIAL, timeMs, cancelled);\n    }\n}\n\nvoid CPointerGesturesProtocol::pinchBegin(uint32_t timeMs, uint32_t fingers) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->m_state.pointerFocusResource.lock());\n\n    for (auto const& sw : m_pinches) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendBegin(SERIAL, timeMs, g_pSeatManager->m_state.pointerFocus->getResource()->resource(), fingers);\n    }\n}\n\nvoid CPointerGesturesProtocol::pinchUpdate(uint32_t timeMs, const Vector2D& delta, double scale, double rotation) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    for (auto const& sw : m_pinches) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendUpdate(timeMs, wl_fixed_from_double(delta.x), wl_fixed_from_double(delta.y), wl_fixed_from_double(scale), wl_fixed_from_double(rotation));\n    }\n}\n\nvoid CPointerGesturesProtocol::pinchEnd(uint32_t timeMs, bool cancelled) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->m_state.pointerFocusResource.lock());\n\n    for (auto const& sw : m_pinches) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendEnd(SERIAL, timeMs, cancelled);\n    }\n}\n\nvoid CPointerGesturesProtocol::holdBegin(uint32_t timeMs, uint32_t fingers) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->m_state.pointerFocusResource.lock());\n\n    for (auto const& sw : m_holds) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendBegin(SERIAL, timeMs, g_pSeatManager->m_state.pointerFocus->getResource()->resource(), fingers);\n    }\n}\n\nvoid CPointerGesturesProtocol::holdEnd(uint32_t timeMs, bool cancelled) {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSEDCLIENT = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->m_state.pointerFocusResource.lock());\n\n    for (auto const& sw : m_holds) {\n        if (sw->m_resource->client() != FOCUSEDCLIENT)\n            continue;\n\n        sw->m_resource->sendEnd(SERIAL, timeMs, cancelled);\n    }\n}\n"
  },
  {
    "path": "src/protocols/PointerGestures.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"pointer-gestures-unstable-v1.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\nclass CPointerGestureSwipe {\n  public:\n    CPointerGestureSwipe(SP<CZwpPointerGestureSwipeV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CZwpPointerGestureSwipeV1> m_resource;\n\n    friend class CPointerGesturesProtocol;\n};\n\nclass CPointerGesturePinch {\n  public:\n    CPointerGesturePinch(SP<CZwpPointerGesturePinchV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CZwpPointerGesturePinchV1> m_resource;\n\n    friend class CPointerGesturesProtocol;\n};\n\nclass CPointerGestureHold {\n  public:\n    CPointerGestureHold(SP<CZwpPointerGestureHoldV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CZwpPointerGestureHoldV1> m_resource;\n\n    friend class CPointerGesturesProtocol;\n};\n\nclass CPointerGesturesProtocol : public IWaylandProtocol {\n  public:\n    CPointerGesturesProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         swipeBegin(uint32_t timeMs, uint32_t fingers);\n    void         swipeUpdate(uint32_t timeMs, const Vector2D& delta);\n    void         swipeEnd(uint32_t timeMs, bool cancelled);\n\n    void         pinchBegin(uint32_t timeMs, uint32_t fingers);\n    void         pinchUpdate(uint32_t timeMs, const Vector2D& delta, double scale, double rotation);\n    void         pinchEnd(uint32_t timeMs, bool cancelled);\n\n    void         holdBegin(uint32_t timeMs, uint32_t fingers);\n    void         holdEnd(uint32_t timeMs, bool cancelled);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void onGestureDestroy(CPointerGestureSwipe* gesture);\n    void onGestureDestroy(CPointerGesturePinch* gesture);\n    void onGestureDestroy(CPointerGestureHold* gesture);\n    void onGetPinchGesture(CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer);\n    void onGetSwipeGesture(CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer);\n    void onGetHoldGesture(CZwpPointerGesturesV1* pMgr, uint32_t id, wl_resource* pointer);\n\n    //\n    std::vector<UP<CZwpPointerGesturesV1>> m_managers;\n    std::vector<UP<CPointerGestureSwipe>>  m_swipes;\n    std::vector<UP<CPointerGesturePinch>>  m_pinches;\n    std::vector<UP<CPointerGestureHold>>   m_holds;\n\n    friend class CPointerGestureHold;\n    friend class CPointerGesturePinch;\n    friend class CPointerGestureSwipe;\n};\n\nnamespace PROTO {\n    inline UP<CPointerGesturesProtocol> pointerGestures;\n};"
  },
  {
    "path": "src/protocols/PointerWarp.cpp",
    "content": "#include \"PointerWarp.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"core/Seat.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../managers/PointerManager.hpp\"\n#include \"../desktop/view/Window.hpp\"\n\nCPointerWarpProtocol::CPointerWarpProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CPointerWarpProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto& RESOURCE = m_managers.emplace_back(makeUnique<CWpPointerWarpV1>(client, ver, id));\n\n    if UNLIKELY (!RESOURCE->resource()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    RESOURCE->setOnDestroy([this](CWpPointerWarpV1* pMgr) { destroyManager(pMgr); });\n    RESOURCE->setDestroy([this](CWpPointerWarpV1* pMgr) { destroyManager(pMgr); });\n\n    RESOURCE->setWarpPointer([](CWpPointerWarpV1* pMgr, wl_resource* surface, wl_resource* pointer, wl_fixed_t x, wl_fixed_t y, uint32_t serial) {\n        const auto PSURFACE = CWLSurfaceResource::fromResource(surface);\n        if (g_pSeatManager->m_state.pointerFocus != PSURFACE)\n            return;\n\n        auto WINDOW = Desktop::View::CWindow::fromView(Desktop::View::CWLSurface::fromResource(PSURFACE)->view());\n        if (!WINDOW)\n            return;\n\n        const auto SURFBOX   = WINDOW->getWindowMainSurfaceBox().expand(1);\n        const auto LOCALPOS  = Vector2D{wl_fixed_to_double(x), wl_fixed_to_double(y)};\n        const auto GLOBALPOS = LOCALPOS + SURFBOX.pos();\n        if (!SURFBOX.containsPoint(GLOBALPOS))\n            return;\n\n        const auto PSEAT = CWLPointerResource::fromResource(pointer)->m_owner.lock();\n        if (!g_pSeatManager->serialValid(PSEAT, serial, false))\n            return;\n\n        LOGM(Log::DEBUG, \"warped pointer to {}\", GLOBALPOS);\n\n        g_pPointerManager->warpTo(GLOBALPOS);\n        g_pSeatManager->sendPointerMotion(Time::millis(Time::steadyNow()), LOCALPOS);\n    });\n}\n\nvoid CPointerWarpProtocol::destroyManager(CWpPointerWarpV1* manager) {\n    std::erase_if(m_managers, [&](const UP<CWpPointerWarpV1>& resource) { return resource.get() == manager; });\n}\n"
  },
  {
    "path": "src/protocols/PointerWarp.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"pointer-warp-v1.hpp\"\n\nclass CPointerWarpProtocol : public IWaylandProtocol {\n  public:\n    CPointerWarpProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyManager(CWpPointerWarpV1* manager);\n\n    //\n    std::vector<UP<CWpPointerWarpV1>> m_managers;\n};\n\nnamespace PROTO {\n    inline UP<CPointerWarpProtocol> pointerWarp;\n};\n"
  },
  {
    "path": "src/protocols/PresentationTime.cpp",
    "content": "#include \"PresentationTime.hpp\"\n#include <algorithm>\n#include \"../helpers/Monitor.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"core/Output.hpp\"\n#include <aquamarine/output/Output.hpp>\n\nCQueuedPresentationData::CQueuedPresentationData(SP<CWLSurfaceResource> surf) : m_surface(surf) {\n    ;\n}\n\nvoid CQueuedPresentationData::setPresentationType(bool zeroCopy_) {\n    m_zeroCopy = zeroCopy_;\n}\n\nvoid CQueuedPresentationData::attachMonitor(PHLMONITOR pMonitor_) {\n    m_monitor = pMonitor_;\n}\n\nvoid CQueuedPresentationData::presented() {\n    m_wasPresented = true;\n}\n\nvoid CQueuedPresentationData::discarded() {\n    m_wasPresented = false;\n}\n\nCPresentationFeedback::CPresentationFeedback(UP<CWpPresentationFeedback>&& resource_, SP<CWLSurfaceResource> surf) : m_resource(std::move(resource_)), m_surface(surf) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWpPresentationFeedback* pMgr) {\n        if (!m_done) // if it's done, it's probably already destroyed. If not, it will be in a sec.\n            PROTO::presentation->destroyResource(this);\n    });\n}\n\nbool CPresentationFeedback::good() {\n    return m_resource->resource();\n}\n\nvoid CPresentationFeedback::sendQueued(WP<CQueuedPresentationData> data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) {\n    auto client = m_resource->client();\n\n    if LIKELY (PROTO::outputs.contains(data->m_monitor->m_name) && data->m_wasPresented) {\n        if LIKELY (auto outputResources = PROTO::outputs.at(data->m_monitor->m_name)->outputResourcesFrom(client); !outputResources.empty()) {\n            for (const auto& r : outputResources) {\n                m_resource->sendSyncOutput(r->getResource()->resource());\n            }\n        }\n    }\n\n    if (data->m_wasPresented) {\n        uint32_t flags = 0;\n        if (!data->m_monitor->m_tearingState.activelyTearing)\n            flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC;\n        if (data->m_zeroCopy)\n            flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY;\n        if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK)\n            flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;\n        if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION)\n            flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION;\n\n        time_t tv_sec = 0;\n        if (sizeof(time_t) > 4)\n            tv_sec = when.tv_sec >> 32;\n\n        uint32_t refreshNs = m_resource->version() == 1 && data->m_monitor->m_vrrActive && data->m_monitor->m_output->vrrCapable ? 0 : untilRefreshNs;\n\n        m_resource->sendPresented(sc<uint32_t>(tv_sec), sc<uint32_t>(when.tv_sec & 0xFFFFFFFF), sc<uint32_t>(when.tv_nsec), refreshNs, sc<uint32_t>(seq >> 32),\n                                  sc<uint32_t>(seq & 0xFFFFFFFF), sc<wpPresentationFeedbackKind>(flags));\n    } else\n        m_resource->sendDiscarded();\n\n    m_done = true;\n}\n\nCPresentationProtocol::CPresentationProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.monitor.removed.listen(\n        [this](PHLMONITOR mon) { std::erase_if(m_queue, [mon](const auto& other) { return !other->m_surface || other->m_monitor == mon; }); });\n}\n\nvoid CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CWpPresentation>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CWpPresentation* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CWpPresentation* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setFeedback([this](CWpPresentation* pMgr, wl_resource* surf, uint32_t id) { this->onGetFeedback(pMgr, surf, id); });\n    RESOURCE->sendClockId(CLOCK_MONOTONIC);\n}\n\nvoid CPresentationProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CPresentationProtocol::destroyResource(CPresentationFeedback* feedback) {\n    std::erase_if(m_feedbacks, [&](const auto& other) { return other.get() == feedback; });\n}\n\nvoid CPresentationProtocol::onGetFeedback(CWpPresentation* pMgr, wl_resource* surf, uint32_t id) {\n    const auto  CLIENT = pMgr->client();\n    const auto& RESOURCE =\n        m_feedbacks.emplace_back(makeUnique<CPresentationFeedback>(makeUnique<CWpPresentationFeedback>(CLIENT, pMgr->version(), id), CWLSurfaceResource::fromResource(surf))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_feedbacks.pop_back();\n        return;\n    }\n}\n\nvoid CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) {\n    for (auto const& feedback : m_feedbacks) {\n        if (!feedback->m_surface)\n            continue;\n\n        for (auto const& data : m_queue) {\n            if (!data->m_surface || data->m_surface != feedback->m_surface || (data->m_monitor && data->m_monitor != pMonitor))\n                continue;\n\n            feedback->sendQueued(data, when, untilRefreshNs, seq, reportedFlags);\n            feedback->m_done = true;\n            break;\n        }\n    }\n\n    if (m_feedbacks.size() > 10000) {\n        LOGM(Log::ERR, \"FIXME: presentation has a feedback leak, and has grown to {} pending entries!!! Dropping!!!!!\", m_feedbacks.size());\n\n        // Move the elements from the 9000th position to the end of the vector.\n        std::vector<UP<CPresentationFeedback>> newFeedbacks;\n        newFeedbacks.reserve(m_feedbacks.size() - 9000);\n\n        for (auto it = m_feedbacks.begin() + 9000; it != m_feedbacks.end(); ++it) {\n            newFeedbacks.push_back(std::move(*it));\n        }\n\n        m_feedbacks = std::move(newFeedbacks);\n    }\n\n    std::erase_if(m_feedbacks, [](const auto& other) { return !other->m_surface || other->m_done; });\n    std::erase_if(m_queue, [pMonitor](const auto& other) { return !other->m_surface || other->m_monitor == pMonitor || !other->m_monitor || other->m_done; });\n}\n\nvoid CPresentationProtocol::queueData(UP<CQueuedPresentationData>&& data) {\n    m_queue.emplace_back(std::move(data));\n}\n"
  },
  {
    "path": "src/protocols/PresentationTime.hpp",
    "content": "#pragma once\n\n#include <ctime>\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"presentation-time.hpp\"\n#include \"../helpers/time/Time.hpp\"\n\nclass CMonitor;\nclass CWLSurfaceResource;\n\nclass CQueuedPresentationData {\n  public:\n    CQueuedPresentationData(SP<CWLSurfaceResource> surf);\n\n    void setPresentationType(bool zeroCopy);\n    void attachMonitor(PHLMONITOR pMonitor);\n\n    void presented();\n    void discarded();\n\n    bool m_done = false;\n\n  private:\n    bool                   m_wasPresented = false;\n    bool                   m_zeroCopy     = false;\n    PHLMONITORREF          m_monitor;\n    WP<CWLSurfaceResource> m_surface;\n\n    friend class CPresentationFeedback;\n    friend class CPresentationProtocol;\n};\n\nclass CPresentationFeedback {\n  public:\n    CPresentationFeedback(UP<CWpPresentationFeedback>&& resource_, SP<CWLSurfaceResource> surf);\n\n    bool good();\n\n    void sendQueued(WP<CQueuedPresentationData> data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags);\n\n  private:\n    UP<CWpPresentationFeedback> m_resource;\n    WP<CWLSurfaceResource>      m_surface;\n    bool                        m_done = false;\n\n    friend class CPresentationProtocol;\n};\n\nclass CPresentationProtocol : public IWaylandProtocol {\n  public:\n    CPresentationProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags);\n    void         queueData(UP<CQueuedPresentationData>&& data);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CPresentationFeedback* feedback);\n    void onGetFeedback(CWpPresentation* pMgr, wl_resource* surf, uint32_t id);\n\n    //\n    std::vector<UP<CWpPresentation>>         m_managers;\n    std::vector<UP<CPresentationFeedback>>   m_feedbacks;\n    std::vector<UP<CQueuedPresentationData>> m_queue;\n\n    friend class CPresentationFeedback;\n};\n\nnamespace PROTO {\n    inline UP<CPresentationProtocol> presentation;\n};\n"
  },
  {
    "path": "src/protocols/PrimarySelection.cpp",
    "content": "#include \"PrimarySelection.hpp\"\n#include <algorithm>\n#include \"../managers/SeatManager.hpp\"\n#include \"core/Seat.hpp\"\n#include \"../config/ConfigValue.hpp\"\nusing namespace Hyprutils::OS;\n\nCPrimarySelectionOffer::CPrimarySelectionOffer(SP<CZwpPrimarySelectionOfferV1> resource_, SP<IDataSource> source_) : m_source(source_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpPrimarySelectionOfferV1* r) { PROTO::primarySelection->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpPrimarySelectionOfferV1* r) { PROTO::primarySelection->destroyResource(this); });\n\n    m_resource->setReceive([this](CZwpPrimarySelectionOfferV1* r, const char* mime, int32_t fd) {\n        CFileDescriptor sendFd{fd};\n        if (!m_source) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer w/o a source\");\n            return;\n        }\n\n        if (m_dead) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer that's dead\");\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"Offer {:x} asks to send data from source {:x}\", (uintptr_t)this, (uintptr_t)m_source.get());\n\n        m_source->send(mime, std::move(sendFd));\n    });\n}\n\nbool CPrimarySelectionOffer::good() {\n    return m_resource->resource();\n}\n\nvoid CPrimarySelectionOffer::sendData() {\n    if UNLIKELY (!m_source)\n        return;\n\n    for (auto const& m : m_source->mimes()) {\n        m_resource->sendOffer(m.c_str());\n    }\n}\n\nCPrimarySelectionSource::CPrimarySelectionSource(SP<CZwpPrimarySelectionSourceV1> resource_, SP<CPrimarySelectionDevice> device_) : m_device(device_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CZwpPrimarySelectionSourceV1* r) {\n        m_events.destroy.emit();\n        PROTO::primarySelection->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CZwpPrimarySelectionSourceV1* r) {\n        m_events.destroy.emit();\n        PROTO::primarySelection->destroyResource(this);\n    });\n\n    m_resource->setOffer([this](CZwpPrimarySelectionSourceV1* r, const char* mime) { m_mimeTypes.emplace_back(mime); });\n}\n\nCPrimarySelectionSource::~CPrimarySelectionSource() {\n    m_events.destroy.emit();\n}\n\nSP<CPrimarySelectionSource> CPrimarySelectionSource::fromResource(wl_resource* res) {\n    auto data = sc<CPrimarySelectionSource*>(sc<CZwpPrimarySelectionSourceV1*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CPrimarySelectionSource::good() {\n    return m_resource->resource();\n}\n\nstd::vector<std::string> CPrimarySelectionSource::mimes() {\n    return m_mimeTypes;\n}\n\nvoid CPrimarySelectionSource::send(const std::string& mime, CFileDescriptor fd) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) {\n        LOGM(Log::ERR, \"Compositor/App bug: CPrimarySelectionSource::sendAskSend with non-existent mime\");\n        return;\n    }\n\n    m_resource->sendSend(mime.c_str(), fd.get());\n}\n\nvoid CPrimarySelectionSource::accepted(const std::string& mime) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end())\n        LOGM(Log::ERR, \"Compositor/App bug: CPrimarySelectionSource::sendAccepted with non-existent mime\");\n\n    // primary sel has no accepted\n}\n\nvoid CPrimarySelectionSource::cancelled() {\n    m_resource->sendCancelled();\n}\n\nvoid CPrimarySelectionSource::error(uint32_t code, const std::string& msg) {\n    m_resource->error(code, msg);\n}\n\nCPrimarySelectionDevice::CPrimarySelectionDevice(SP<CZwpPrimarySelectionDeviceV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CZwpPrimarySelectionDeviceV1* r) { PROTO::primarySelection->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpPrimarySelectionDeviceV1* r) { PROTO::primarySelection->destroyResource(this); });\n\n    m_resource->setSetSelection([](CZwpPrimarySelectionDeviceV1* r, wl_resource* sourceR, uint32_t serial) {\n        static auto PPRIMARYSEL = CConfigValue<Hyprlang::INT>(\"misc:middle_click_paste\");\n\n        if (!*PPRIMARYSEL) {\n            LOGM(Log::DEBUG, \"Ignoring primary selection: disabled in config\");\n            g_pSeatManager->setCurrentPrimarySelection(nullptr);\n            return;\n        }\n\n        auto source = sourceR ? CPrimarySelectionSource::fromResource(sourceR) : CSharedPointer<CPrimarySelectionSource>{};\n        if (!source) {\n            LOGM(Log::DEBUG, \"wlr reset selection received\");\n            g_pSeatManager->setCurrentPrimarySelection(nullptr);\n            return;\n        }\n\n        if (source && source->used())\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        LOGM(Log::DEBUG, \"wlr manager requests selection to {:x}\", (uintptr_t)source.get());\n        g_pSeatManager->setCurrentPrimarySelection(source);\n    });\n}\n\nbool CPrimarySelectionDevice::good() {\n    return m_resource->resource();\n}\n\nwl_client* CPrimarySelectionDevice::client() {\n    return m_client;\n}\n\nvoid CPrimarySelectionDevice::sendDataOffer(SP<CPrimarySelectionOffer> offer) {\n    m_resource->sendDataOffer(offer->m_resource.get());\n}\n\nvoid CPrimarySelectionDevice::sendSelection(SP<CPrimarySelectionOffer> selection) {\n    if (!selection)\n        m_resource->sendSelectionRaw(nullptr);\n    else\n        m_resource->sendSelection(selection->m_resource.get());\n}\n\nCPrimarySelectionManager::CPrimarySelectionManager(SP<CZwpPrimarySelectionDeviceManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpPrimarySelectionDeviceManagerV1* r) { PROTO::primarySelection->destroyResource(this); });\n\n    m_resource->setGetDevice([this](CZwpPrimarySelectionDeviceManagerV1* r, uint32_t id, wl_resource* seat) {\n        const auto RESOURCE =\n            PROTO::primarySelection->m_devices.emplace_back(makeShared<CPrimarySelectionDevice>(makeShared<CZwpPrimarySelectionDeviceV1>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::primarySelection->m_devices.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n        m_device         = RESOURCE;\n\n        for (auto const& s : m_sources) {\n            if (!s)\n                continue;\n            s->m_device = RESOURCE;\n        }\n\n        LOGM(Log::DEBUG, \"New primary selection data device bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n\n    m_resource->setCreateSource([this](CZwpPrimarySelectionDeviceManagerV1* r, uint32_t id) {\n        std::erase_if(m_sources, [](const auto& e) { return e.expired(); });\n\n        const auto RESOURCE = PROTO::primarySelection->m_sources.emplace_back(\n            makeShared<CPrimarySelectionSource>(makeShared<CZwpPrimarySelectionSourceV1>(r->client(), r->version(), id), m_device.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::primarySelection->m_sources.pop_back();\n            return;\n        }\n\n        if (!m_device)\n            LOGM(Log::WARN, \"New data source before a device was created\");\n\n        RESOURCE->m_self = RESOURCE;\n\n        m_sources.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New primary selection data source bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n}\n\nbool CPrimarySelectionManager::good() {\n    return m_resource->resource();\n}\n\nCPrimarySelectionProtocol::CPrimarySelectionProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CPrimarySelectionProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CPrimarySelectionManager>(makeShared<CZwpPrimarySelectionDeviceManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New primary_seletion_manager at {:x}\", (uintptr_t)RESOURCE.get());\n\n    // we need to do it here because protocols come before seatMgr\n    if (!m_listeners.onPointerFocusChange)\n        m_listeners.onPointerFocusChange = g_pSeatManager->m_events.pointerFocusChange.listen([this] { this->onPointerFocus(); });\n}\n\nvoid CPrimarySelectionProtocol::destroyResource(CPrimarySelectionManager* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CPrimarySelectionProtocol::destroyResource(CPrimarySelectionSource* resource) {\n    std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CPrimarySelectionProtocol::destroyResource(CPrimarySelectionDevice* resource) {\n    std::erase_if(m_devices, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CPrimarySelectionProtocol::destroyResource(CPrimarySelectionOffer* resource) {\n    std::erase_if(m_offers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CPrimarySelectionProtocol::sendSelectionToDevice(SP<CPrimarySelectionDevice> dev, SP<IDataSource> sel) {\n    if (!sel) {\n        dev->sendSelection(nullptr);\n        return;\n    }\n\n    const auto OFFER =\n        m_offers.emplace_back(makeShared<CPrimarySelectionOffer>(makeShared<CZwpPrimarySelectionOfferV1>(dev->m_resource->client(), dev->m_resource->version(), 0), sel));\n\n    if (!OFFER->good()) {\n        dev->m_resource->noMemory();\n        m_offers.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New offer {:x} for data source {:x}\", (uintptr_t)OFFER.get(), (uintptr_t)sel.get());\n\n    dev->sendDataOffer(OFFER);\n    OFFER->sendData();\n    dev->sendSelection(OFFER);\n}\n\nvoid CPrimarySelectionProtocol::setSelection(SP<IDataSource> source) {\n    for (auto const& o : m_offers) {\n        if (o->m_source && o->m_source->hasDnd())\n            continue;\n        o->m_dead = true;\n    }\n\n    if (!source) {\n        LOGM(Log::DEBUG, \"resetting selection\");\n\n        if (!g_pSeatManager->m_state.pointerFocusResource)\n            return;\n\n        auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.pointerFocusResource->client());\n        if (DESTDEVICE)\n            sendSelectionToDevice(DESTDEVICE, nullptr);\n\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New selection for data source {:x}\", (uintptr_t)source.get());\n\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.pointerFocusResource->client());\n\n    if (!DESTDEVICE) {\n        LOGM(Log::DEBUG, \"CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device\");\n        g_pSeatManager->m_selection.currentPrimarySelection.reset();\n        return;\n    }\n\n    sendSelectionToDevice(DESTDEVICE, source);\n}\n\nvoid CPrimarySelectionProtocol::updateSelection() {\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    auto selection  = g_pSeatManager->m_selection.currentPrimarySelection.lock();\n    auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.pointerFocusResource->client());\n\n    if (!selection || !DESTDEVICE) {\n        LOGM(Log::DEBUG, \"CPrimarySelectionProtocol::updateSelection: cannot send selection to a client without a data_device\");\n        return;\n    }\n\n    sendSelectionToDevice(DESTDEVICE, g_pSeatManager->m_selection.currentPrimarySelection.lock());\n}\n\nvoid CPrimarySelectionProtocol::onPointerFocus() {\n    for (auto const& o : m_offers) {\n        o->m_dead = true;\n    }\n\n    updateSelection();\n}\n\nSP<CPrimarySelectionDevice> CPrimarySelectionProtocol::dataDeviceForClient(wl_client* c) {\n    auto it = std::ranges::find_if(m_devices, [c](const auto& e) { return e->client() == c; });\n    if (it == m_devices.end())\n        return nullptr;\n    return *it;\n}\n"
  },
  {
    "path": "src/protocols/PrimarySelection.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"primary-selection-unstable-v1.hpp\"\n#include \"types/DataDevice.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CPrimarySelectionOffer;\nclass CPrimarySelectionSource;\nclass CPrimarySelectionDevice;\nclass CPrimarySelectionManager;\n\nclass CPrimarySelectionOffer {\n  public:\n    CPrimarySelectionOffer(SP<CZwpPrimarySelectionOfferV1> resource_, SP<IDataSource> source_);\n\n    bool            good();\n    void            sendData();\n\n    bool            m_dead = false;\n\n    WP<IDataSource> m_source;\n\n  private:\n    SP<CZwpPrimarySelectionOfferV1> m_resource;\n\n    friend class CPrimarySelectionDevice;\n};\n\nclass CPrimarySelectionSource : public IDataSource {\n  public:\n    CPrimarySelectionSource(SP<CZwpPrimarySelectionSourceV1> resource_, SP<CPrimarySelectionDevice> device_);\n    ~CPrimarySelectionSource();\n\n    static SP<CPrimarySelectionSource> fromResource(wl_resource*);\n\n    bool                               good();\n\n    virtual std::vector<std::string>   mimes();\n    virtual void                       send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd);\n    virtual void                       accepted(const std::string& mime);\n    virtual void                       cancelled();\n    virtual void                       error(uint32_t code, const std::string& msg);\n\n    std::vector<std::string>           m_mimeTypes;\n    WP<CPrimarySelectionSource>        m_self;\n    WP<CPrimarySelectionDevice>        m_device;\n\n  private:\n    SP<CZwpPrimarySelectionSourceV1> m_resource;\n};\n\nclass CPrimarySelectionDevice {\n  public:\n    CPrimarySelectionDevice(SP<CZwpPrimarySelectionDeviceV1> resource_);\n\n    bool                        good();\n    wl_client*                  client();\n\n    void                        sendDataOffer(SP<CPrimarySelectionOffer> offer);\n    void                        sendSelection(SP<CPrimarySelectionOffer> selection);\n\n    WP<CPrimarySelectionDevice> m_self;\n\n  private:\n    SP<CZwpPrimarySelectionDeviceV1> m_resource;\n    wl_client*                       m_client = nullptr;\n\n    friend class CPrimarySelectionProtocol;\n};\n\nclass CPrimarySelectionManager {\n  public:\n    CPrimarySelectionManager(SP<CZwpPrimarySelectionDeviceManagerV1> resource_);\n\n    bool                                     good();\n\n    WP<CPrimarySelectionDevice>              m_device;\n    std::vector<WP<CPrimarySelectionSource>> m_sources;\n\n  private:\n    SP<CZwpPrimarySelectionDeviceManagerV1> m_resource;\n};\n\nclass CPrimarySelectionProtocol : public IWaylandProtocol {\n  public:\n    CPrimarySelectionProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CPrimarySelectionManager* resource);\n    void destroyResource(CPrimarySelectionDevice* resource);\n    void destroyResource(CPrimarySelectionSource* resource);\n    void destroyResource(CPrimarySelectionOffer* resource);\n\n    //\n    std::vector<SP<CPrimarySelectionManager>> m_managers;\n    std::vector<SP<CPrimarySelectionDevice>>  m_devices;\n    std::vector<SP<CPrimarySelectionSource>>  m_sources;\n    std::vector<SP<CPrimarySelectionOffer>>   m_offers;\n\n    //\n    void setSelection(SP<IDataSource> source);\n    void sendSelectionToDevice(SP<CPrimarySelectionDevice> dev, SP<IDataSource> sel);\n    void updateSelection();\n    void onPointerFocus();\n\n    //\n    SP<CPrimarySelectionDevice> dataDeviceForClient(wl_client*);\n\n    friend class CPrimarySelectionManager;\n    friend class CPrimarySelectionDevice;\n    friend class CPrimarySelectionSource;\n    friend class CPrimarySelectionOffer;\n    friend class CSeatManager;\n\n    struct {\n        CHyprSignalListener onPointerFocusChange;\n    } m_listeners;\n};\n\nnamespace PROTO {\n    inline UP<CPrimarySelectionProtocol> primarySelection;\n};\n"
  },
  {
    "path": "src/protocols/RelativePointer.cpp",
    "content": "#include \"RelativePointer.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"core/Seat.hpp\"\n#include <algorithm>\n\nCRelativePointer::CRelativePointer(SP<CZwpRelativePointerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setDestroy([this](CZwpRelativePointerV1* pMgr) { PROTO::relativePointer->destroyRelativePointer(this); });\n    m_resource->setOnDestroy([this](CZwpRelativePointerV1* pMgr) { PROTO::relativePointer->destroyRelativePointer(this); });\n}\n\nbool CRelativePointer::good() {\n    return m_resource->resource();\n}\n\nwl_client* CRelativePointer::client() {\n    return m_client;\n}\n\nvoid CRelativePointer::sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) {\n    m_resource->sendRelativeMotion(time >> 32, time & 0xFFFFFFFF, wl_fixed_from_double(delta.x), wl_fixed_from_double(delta.y), wl_fixed_from_double(deltaUnaccel.x),\n                                   wl_fixed_from_double(deltaUnaccel.y));\n}\n\nCRelativePointerProtocol::CRelativePointerProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CRelativePointerProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpRelativePointerManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpRelativePointerManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpRelativePointerManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetRelativePointer([this](CZwpRelativePointerManagerV1* pMgr, uint32_t id, wl_resource* pointer) { this->onGetRelativePointer(pMgr, id, pointer); });\n}\n\nvoid CRelativePointerProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CRelativePointerProtocol::destroyRelativePointer(CRelativePointer* pointer) {\n    std::erase_if(m_relativePointers, [&](const auto& other) { return other.get() == pointer; });\n}\n\nvoid CRelativePointerProtocol::onGetRelativePointer(CZwpRelativePointerManagerV1* pMgr, uint32_t id, wl_resource* pointer) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_relativePointers.emplace_back(makeUnique<CRelativePointer>(makeShared<CZwpRelativePointerV1>(CLIENT, pMgr->version(), id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_relativePointers.pop_back();\n        return;\n    }\n}\n\nvoid CRelativePointerProtocol::sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) {\n\n    if (!g_pSeatManager->m_state.pointerFocusResource)\n        return;\n\n    const auto FOCUSED = g_pSeatManager->m_state.pointerFocusResource->client();\n\n    for (auto const& rp : m_relativePointers) {\n        if (FOCUSED != rp->client())\n            continue;\n\n        rp->sendRelativeMotion(time, delta, deltaUnaccel);\n    }\n}\n"
  },
  {
    "path": "src/protocols/RelativePointer.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"relative-pointer-unstable-v1.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\nclass CRelativePointer {\n  public:\n    CRelativePointer(SP<CZwpRelativePointerV1> resource_);\n\n    void       sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel);\n\n    bool       good();\n    wl_client* client();\n\n  private:\n    SP<CZwpRelativePointerV1> m_resource;\n    wl_client*                m_client = nullptr;\n};\n\nclass CRelativePointerProtocol : public IWaylandProtocol {\n  public:\n    CRelativePointerProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyRelativePointer(CRelativePointer* pointer);\n    void onGetRelativePointer(CZwpRelativePointerManagerV1* pMgr, uint32_t id, wl_resource* pointer);\n\n    //\n    std::vector<UP<CZwpRelativePointerManagerV1>> m_managers;\n    std::vector<UP<CRelativePointer>>             m_relativePointers;\n\n    friend class CRelativePointer;\n};\n\nnamespace PROTO {\n    inline UP<CRelativePointerProtocol> relativePointer;\n};"
  },
  {
    "path": "src/protocols/Screencopy.cpp",
    "content": "#include \"Screencopy.hpp\"\n#include \"../managers/screenshare/ScreenshareManager.hpp\"\n#include \"core/Output.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"types/Buffer.hpp\"\n#include \"../helpers/Format.hpp\"\n#include \"../helpers/time/Time.hpp\"\n\nusing namespace Screenshare;\n\nCScreencopyClient::CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); });\n    m_resource->setCaptureOutput(\n        [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { captureOutput(frame, overlayCursor, output, {}); });\n    m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w,\n                                              int32_t h) { captureOutput(frame, overlayCursor, output, {x, y, w, h}); });\n\n    m_savedClient = m_resource->client();\n}\n\nvoid CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) {\n    const auto PMONITORRES = CWLOutputResource::fromResource(output);\n    if (!PMONITORRES || !PMONITORRES->m_monitor) {\n        LOGM(Log::ERR, \"Tried to capture invalid output/monitor in {:x}\", (uintptr_t)this);\n        m_resource->error(-1, \"invalid output\");\n        return;\n    }\n\n    const auto PMONITOR = PMONITORRES->m_monitor.lock();\n    auto       session  = box.w == 0 && box.h == 0 ? Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR) :\n                                                     Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR, box);\n\n    const auto FRAME = PROTO::screencopy->m_frames.emplace_back(\n        makeShared<CScreencopyFrame>(makeShared<CZwlrScreencopyFrameV1>(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_));\n\n    if (!FRAME->good()) {\n        LOGM(Log::ERR, \"Couldn't alloc frame for sharing! (no memory)\");\n        m_resource->noMemory();\n        PROTO::screencopy->destroyResource(FRAME.get());\n        return;\n    }\n\n    FRAME->m_client = m_self;\n    FRAME->m_self   = FRAME;\n}\n\nbool CScreencopyClient::good() {\n    return m_resource && m_resource->resource();\n}\n\nCScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, WP<CScreenshareSession> session, bool overlayCursor) :\n    m_resource(resource_), m_session(session), m_overlayCursor(overlayCursor) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwlrScreencopyFrameV1* pMgr) { PROTO::screencopy->destroyResource(this); });\n    m_resource->setDestroy([this](CZwlrScreencopyFrameV1* pFrame) { PROTO::screencopy->destroyResource(this); });\n    m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, false); });\n    m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, true); });\n\n    m_frame = m_session->nextFrame(overlayCursor);\n\n    auto formats = m_session->allowedFormats();\n    if (formats.empty()) {\n        LOGM(Log::ERR, \"No format supported by renderer in screencopy protocol\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    DRMFormat  format  = formats.at(0);\n    auto       bufSize = m_frame->bufferSize();\n\n    const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format);\n\n    if (!PSHMINFO) {\n        LOGM(Log::ERR, \"No pixel format for drm format\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x);\n    m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride);\n\n    if (m_resource->version() >= 3) {\n        if LIKELY (format != DRM_FORMAT_INVALID)\n            m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y);\n\n        m_resource->sendBufferDone();\n    }\n}\n\nvoid CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage) {\n    if UNLIKELY (!good()) {\n        LOGM(Log::ERR, \"No frame in shareFrame??\");\n        return;\n    }\n\n    if UNLIKELY (m_session.expired() || !m_session->monitor()) {\n        LOGM(Log::ERR, \"Session stopped for frame {:x}\", (uintptr_t)this);\n        m_resource->sendFailed();\n        return;\n    }\n\n    if UNLIKELY (m_buffer) {\n        LOGM(Log::ERR, \"Buffer used in {:x}\", (uintptr_t)this);\n        m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, \"frame already used\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    const auto PBUFFERRES = CWLBufferResource::fromResource(buffer);\n    if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) {\n        LOGM(Log::ERR, \"Invalid buffer in {:x}\", (uintptr_t)this);\n        m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, \"invalid buffer\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    const auto& PBUFFER = PBUFFERRES->m_buffer.lock();\n\n    if (!withDamage)\n        g_pHyprRenderer->damageMonitor(m_session->monitor());\n\n    auto error = m_frame->share(PBUFFER, {}, [this, withDamage, self = m_self](eScreenshareResult result) {\n        if (self.expired() || !good())\n            return;\n        switch (result) {\n            case RESULT_COPIED: {\n                m_resource->sendFlags(sc<zwlrScreencopyFrameV1Flags>(0));\n                if (withDamage)\n                    m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); });\n\n                const auto [sec, nsec] = Time::secNsec(m_timestamp);\n                uint32_t tvSecHi       = (sizeof(sec) > 4) ? sec >> 32 : 0;\n                uint32_t tvSecLo       = sec & 0xFFFFFFFF;\n                m_resource->sendReady(tvSecHi, tvSecLo, nsec);\n                break;\n            }\n            case RESULT_NOT_COPIED:\n                LOGM(Log::ERR, \"Frame share failed in {:x}\", (uintptr_t)this);\n                m_resource->sendFailed();\n                break;\n            case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break;\n        }\n    });\n\n    switch (error) {\n        case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break;\n        case ERROR_NO_BUFFER: m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, \"invalid buffer\"); break;\n        case ERROR_BUFFER_SIZE:\n        case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break;\n        case ERROR_UNKNOWN:\n        case ERROR_STOPPED: m_resource->sendFailed(); break;\n    }\n}\n\nbool CScreencopyFrame::good() {\n    return m_resource && m_resource->resource();\n}\n\nCScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto CLIENT = m_clients.emplace_back(makeShared<CScreencopyClient>(makeShared<CZwlrScreencopyManagerV1>(client, ver, id)));\n\n    if (!CLIENT->good()) {\n        LOGM(Log::DEBUG, \"Failed to bind client! (out of memory)\");\n        CLIENT->m_resource->noMemory();\n        m_clients.pop_back();\n        return;\n    }\n\n    CLIENT->m_self = CLIENT;\n\n    LOGM(Log::DEBUG, \"Bound client successfully!\");\n}\n\nvoid CScreencopyProtocol::destroyResource(CScreencopyClient* client) {\n    std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; });\n    std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; });\n}\n\nvoid CScreencopyProtocol::destroyResource(CScreencopyFrame* frame) {\n    std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; });\n}\n"
  },
  {
    "path": "src/protocols/Screencopy.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-screencopy-unstable-v1.hpp\"\n\n#include \"../helpers/time/Time.hpp\"\n#include \"./types/Buffer.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n\n#include <vector>\n\nclass CMonitor;\nclass IHLBuffer;\nnamespace Screenshare {\n    class CScreenshareSession;\n    class CScreenshareFrame;\n};\n\nclass CScreencopyClient {\n  public:\n    CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CZwlrScreencopyManagerV1> m_resource;\n    WP<CScreencopyClient>        m_self;\n\n    wl_client*                   m_savedClient = nullptr;\n\n    void                         captureOutput(uint32_t frame, int32_t overlayCursor, wl_resource* output, CBox box);\n\n    friend class CScreencopyProtocol;\n};\n\nclass CScreencopyFrame {\n  public:\n    CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource, WP<Screenshare::CScreenshareSession> session, bool overlayCursor);\n\n    bool good();\n\n  private:\n    SP<CZwlrScreencopyFrameV1>           m_resource;\n    WP<CScreencopyFrame>                 m_self;\n    WP<CScreencopyClient>                m_client;\n\n    WP<Screenshare::CScreenshareSession> m_session;\n    UP<Screenshare::CScreenshareFrame>   m_frame;\n\n    CHLBufferReference                   m_buffer;\n    Time::steady_tp                      m_timestamp;\n    bool                                 m_overlayCursor = true;\n\n    //\n    void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage);\n\n    friend class CScreencopyProtocol;\n    friend class CScreencopyClient;\n};\n\nclass CScreencopyProtocol : public IWaylandProtocol {\n  public:\n    CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t version, uint32_t id);\n    void         destroyResource(CScreencopyClient* resource);\n    void         destroyResource(CScreencopyFrame* resource);\n\n  private:\n    std::vector<SP<CScreencopyFrame>>  m_frames;\n    std::vector<SP<CScreencopyClient>> m_clients;\n\n    friend class CScreencopyFrame;\n    friend class CScreencopyClient;\n};\n\nnamespace PROTO {\n    inline UP<CScreencopyProtocol> screencopy;\n};\n"
  },
  {
    "path": "src/protocols/SecurityContext.cpp",
    "content": "#include \"SecurityContext.hpp\"\n#include \"../Compositor.hpp\"\n#include <sys/socket.h>\nusing namespace Hyprutils::OS;\n\nstatic int onListenFdEvent(int fd, uint32_t mask, void* data) {\n    auto secCtx = sc<CSecurityContext*>(data);\n    secCtx->onListen(mask);\n    return 0;\n}\n\nstatic int onCloseFdEvent(int fd, uint32_t mask, void* data) {\n    auto secCtx = sc<CSecurityContext*>(data);\n    secCtx->onClose(mask);\n    return 0;\n}\n\nSP<CSecurityContextSandboxedClient> CSecurityContextSandboxedClient::create(CFileDescriptor clientFD_) {\n    auto p = SP<CSecurityContextSandboxedClient>(new CSecurityContextSandboxedClient(std::move(clientFD_)));\n    if (!p->m_client)\n        return nullptr;\n    return p;\n}\n\nstatic void onSecurityContextClientDestroy(wl_listener* l, void* d) {\n    SCSecurityContextSandboxedClientDestroyWrapper* wrap   = wl_container_of(l, wrap, listener);\n    CSecurityContextSandboxedClient*                client = wrap->parent;\n    client->onDestroy();\n}\n\nCSecurityContextSandboxedClient::CSecurityContextSandboxedClient(CFileDescriptor clientFD_) : m_clientFD(std::move(clientFD_)) {\n    m_client = wl_client_create(g_pCompositor->m_wlDisplay, m_clientFD.get());\n    if (!m_client)\n        return;\n\n    wl_list_init(&m_destroyListener.listener.link);\n    m_destroyListener.listener.notify = ::onSecurityContextClientDestroy;\n    m_destroyListener.parent          = this;\n    wl_client_add_destroy_late_listener(m_client, &m_destroyListener.listener);\n}\n\nCSecurityContextSandboxedClient::~CSecurityContextSandboxedClient() {\n    wl_list_remove(&m_destroyListener.listener.link);\n    wl_list_init(&m_destroyListener.listener.link);\n}\n\nvoid CSecurityContextSandboxedClient::onDestroy() {\n    std::erase_if(PROTO::securityContext->m_sandboxedClients, [this](const auto& e) { return e.get() == this; });\n}\n\nCSecurityContext::CSecurityContext(SP<CWpSecurityContextV1> resource_, int listenFD_, int closeFD_) : m_listenFD(listenFD_), m_closeFD(closeFD_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWpSecurityContextV1* r) {\n        LOGM(Log::DEBUG, \"security_context at 0x{:x}: resource destroyed, keeping context until fd hangup\", (uintptr_t)this);\n        m_resource = nullptr;\n    });\n    m_resource->setOnDestroy([this](CWpSecurityContextV1* r) {\n        LOGM(Log::DEBUG, \"security_context at 0x{:x}: resource destroyed, keeping context until fd hangup\", (uintptr_t)this);\n        m_resource = nullptr;\n    });\n\n    LOGM(Log::DEBUG, \"New security_context at 0x{:x}\", (uintptr_t)this);\n\n    m_resource->setSetSandboxEngine([this](CWpSecurityContextV1* r, const char* engine) {\n        if UNLIKELY (!m_sandboxEngine.empty()) {\n            r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, \"Sandbox engine already set\");\n            return;\n        }\n\n        if UNLIKELY (m_committed) {\n            r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, \"Context already committed\");\n            return;\n        }\n\n        m_sandboxEngine = engine ? engine : \"(null)\";\n        LOGM(Log::DEBUG, \"security_context at 0x{:x} sets engine to {}\", (uintptr_t)this, m_sandboxEngine);\n    });\n\n    m_resource->setSetAppId([this](CWpSecurityContextV1* r, const char* appid) {\n        if UNLIKELY (!m_appID.empty()) {\n            r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, \"Sandbox appid already set\");\n            return;\n        }\n\n        if UNLIKELY (m_committed) {\n            r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, \"Context already committed\");\n            return;\n        }\n\n        m_appID = appid ? appid : \"(null)\";\n        LOGM(Log::DEBUG, \"security_context at 0x{:x} sets appid to {}\", (uintptr_t)this, m_appID);\n    });\n\n    m_resource->setSetInstanceId([this](CWpSecurityContextV1* r, const char* instance) {\n        if UNLIKELY (!m_instanceID.empty()) {\n            r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, \"Sandbox instance already set\");\n            return;\n        }\n\n        if UNLIKELY (m_committed) {\n            r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, \"Context already committed\");\n            return;\n        }\n\n        m_instanceID = instance ? instance : \"(null)\";\n        LOGM(Log::DEBUG, \"security_context at 0x{:x} sets instance to {}\", (uintptr_t)this, m_instanceID);\n    });\n\n    m_resource->setCommit([this](CWpSecurityContextV1* r) {\n        m_committed = true;\n\n        LOGM(Log::DEBUG, \"security_context at 0x{:x} commits\", (uintptr_t)this);\n\n        m_listenSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_listenFD.get(), WL_EVENT_READABLE, ::onListenFdEvent, this);\n        m_closeSource  = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_closeFD.get(), 0, ::onCloseFdEvent, this);\n\n        if (!m_listenSource || !m_closeSource) {\n            r->noMemory();\n            return;\n        }\n    });\n}\n\nCSecurityContext::~CSecurityContext() {\n    if (m_listenSource)\n        wl_event_source_remove(m_listenSource);\n    if (m_closeSource)\n        wl_event_source_remove(m_closeSource);\n}\n\nbool CSecurityContext::good() {\n    return m_resource->resource();\n}\n\nvoid CSecurityContext::onListen(uint32_t mask) {\n    if UNLIKELY (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {\n        LOGM(Log::ERR, \"security_context at 0x{:x} got an error in listen\", (uintptr_t)this);\n        PROTO::securityContext->destroyContext(this);\n        return;\n    }\n\n    if (!(mask & WL_EVENT_READABLE))\n        return;\n\n    CFileDescriptor clientFD{accept(m_listenFD.get(), nullptr, nullptr)};\n    if UNLIKELY (!clientFD.isValid()) {\n        LOGM(Log::ERR, \"security_context at 0x{:x} couldn't accept\", (uintptr_t)this);\n        return;\n    }\n\n    auto newClient = CSecurityContextSandboxedClient::create(std::move(clientFD));\n    if UNLIKELY (!newClient) {\n        LOGM(Log::ERR, \"security_context at 0x{:x} couldn't create a client\", (uintptr_t)this);\n        return;\n    }\n\n    PROTO::securityContext->m_sandboxedClients.emplace_back(newClient);\n\n    LOGM(Log::DEBUG, \"security_context at 0x{:x} got a new wl_client 0x{:x}\", (uintptr_t)this, (uintptr_t)newClient->m_client);\n}\n\nvoid CSecurityContext::onClose(uint32_t mask) {\n    if (!(mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)))\n        return;\n\n    PROTO::securityContext->destroyContext(this);\n}\n\nCSecurityContextManagerResource::CSecurityContextManagerResource(SP<CWpSecurityContextManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWpSecurityContextManagerV1* r) { PROTO::securityContext->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpSecurityContextManagerV1* r) { PROTO::securityContext->destroyResource(this); });\n\n    m_resource->setCreateListener([](CWpSecurityContextManagerV1* r, uint32_t id, int32_t lfd, int32_t cfd) {\n        const auto RESOURCE =\n            PROTO::securityContext->m_contexts.emplace_back(makeShared<CSecurityContext>(makeShared<CWpSecurityContextV1>(r->client(), r->version(), id), lfd, cfd));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::securityContext->m_contexts.pop_back();\n            return;\n        }\n    });\n}\n\nbool CSecurityContextManagerResource::good() {\n    return m_resource->resource();\n}\n\nCSecurityContextProtocol::CSecurityContextProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CSecurityContextProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CSecurityContextManagerResource>(makeShared<CWpSecurityContextManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CSecurityContextProtocol::destroyResource(CSecurityContextManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n\nvoid CSecurityContextProtocol::destroyContext(CSecurityContext* context) {\n    std::erase_if(m_contexts, [&](const auto& other) { return other.get() == context; });\n}\n\nbool CSecurityContextProtocol::isClientSandboxed(const wl_client* client) {\n    return std::ranges::find_if(m_sandboxedClients, [client](const auto& e) { return e->m_client == client; }) != m_sandboxedClients.end();\n}\n"
  },
  {
    "path": "src/protocols/SecurityContext.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"security-context-v1.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CSecurityContext {\n  public:\n    CSecurityContext(SP<CWpSecurityContextV1> resource_, int listenFD_, int closeFD_);\n    ~CSecurityContext();\n\n    bool                           good();\n\n    std::string                    m_sandboxEngine;\n    std::string                    m_appID;\n    std::string                    m_instanceID;\n\n    Hyprutils::OS::CFileDescriptor m_listenFD;\n    Hyprutils::OS::CFileDescriptor m_closeFD;\n\n    void                           onListen(uint32_t mask);\n    void                           onClose(uint32_t mask);\n\n  private:\n    SP<CWpSecurityContextV1> m_resource;\n\n    wl_event_source*         m_listenSource = nullptr;\n    wl_event_source*         m_closeSource  = nullptr;\n\n    bool                     m_committed = false;\n};\n\nclass CSecurityContextManagerResource {\n  public:\n    CSecurityContextManagerResource(SP<CWpSecurityContextManagerV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CWpSecurityContextManagerV1> m_resource;\n};\n\nclass CSecurityContextSandboxedClient;\nstruct SCSecurityContextSandboxedClientDestroyWrapper {\n    wl_listener                      listener;\n    CSecurityContextSandboxedClient* parent = nullptr;\n};\n\nclass CSecurityContextSandboxedClient {\n  public:\n    static SP<CSecurityContextSandboxedClient> create(Hyprutils::OS::CFileDescriptor clientFD);\n    ~CSecurityContextSandboxedClient();\n\n    void                                           onDestroy();\n\n    SCSecurityContextSandboxedClientDestroyWrapper m_destroyListener;\n\n  private:\n    CSecurityContextSandboxedClient(Hyprutils::OS::CFileDescriptor clientFD_);\n\n    wl_client*                     m_client = nullptr;\n    Hyprutils::OS::CFileDescriptor m_clientFD;\n\n    friend class CSecurityContextProtocol;\n    friend class CSecurityContext;\n};\n\nclass CSecurityContextProtocol : public IWaylandProtocol {\n  public:\n    CSecurityContextProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    bool         isClientSandboxed(const wl_client* client);\n\n  private:\n    void destroyResource(CSecurityContextManagerResource* resource);\n\n    void destroyContext(CSecurityContext* context);\n\n    //\n    std::vector<SP<CSecurityContextManagerResource>> m_managers;\n    std::vector<SP<CSecurityContext>>                m_contexts;\n    std::vector<SP<CSecurityContextSandboxedClient>> m_sandboxedClients;\n\n    friend class CSecurityContextManagerResource;\n    friend class CSecurityContext;\n    friend class CSecurityContextSandboxedClient;\n};\n\nnamespace PROTO {\n    inline UP<CSecurityContextProtocol> securityContext;\n};\n"
  },
  {
    "path": "src/protocols/ServerDecorationKDE.cpp",
    "content": "#include \"ServerDecorationKDE.hpp\"\n#include \"core/Compositor.hpp\"\n\nCServerDecorationKDE::CServerDecorationKDE(SP<COrgKdeKwinServerDecoration> resource_, SP<CWLSurfaceResource> surf_) : m_surf(surf_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](COrgKdeKwinServerDecoration* pMgr) { PROTO::serverDecorationKDE->destroyResource(this); });\n    m_resource->setOnDestroy([this](COrgKdeKwinServerDecoration* pMgr) { PROTO::serverDecorationKDE->destroyResource(this); });\n    m_resource->setRequestMode([this](COrgKdeKwinServerDecoration*, uint32_t mode) {\n        if (m_requestsSent > 3)\n            return; // don't start a tug of war\n\n        auto sendMode = kdeModeOnRequestCSD(mode);\n        m_resource->sendMode(sendMode);\n        m_mostRecentlySent      = sendMode;\n        m_mostRecentlyRequested = mode;\n        m_requestsSent++;\n    });\n\n    // we send this and ignore request_mode.\n    auto sendMode = kdeDefaultModeCSD();\n    m_resource->sendMode(sendMode);\n    m_mostRecentlySent = sendMode;\n}\n\nbool CServerDecorationKDE::good() {\n    return m_resource->resource();\n}\n\nuint32_t CServerDecorationKDE::kdeDefaultModeCSD() {\n    return ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER;\n}\n\nuint32_t CServerDecorationKDE::kdeModeOnRequestCSD(uint32_t modeRequestedByClient) {\n    return kdeDefaultModeCSD();\n}\n\nuint32_t CServerDecorationKDE::kdeModeOnReleaseCSD() {\n    return kdeDefaultModeCSD();\n}\n\nCServerDecorationKDEProtocol::CServerDecorationKDEProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CServerDecorationKDEProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<COrgKdeKwinServerDecorationManager>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](COrgKdeKwinServerDecorationManager* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setCreate([this](COrgKdeKwinServerDecorationManager* pMgr, uint32_t id, wl_resource* pointer) { this->createDecoration(pMgr, id, pointer); });\n\n    // send default mode of SSD, as Hyprland will never ask for CSD. Screw Gnome and GTK.\n    RESOURCE->sendDefaultMode(kdeDefaultManagerModeCSD());\n}\n\nuint32_t CServerDecorationKDEProtocol::kdeDefaultManagerModeCSD() {\n    return ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER;\n}\n\nvoid CServerDecorationKDEProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CServerDecorationKDEProtocol::destroyResource(CServerDecorationKDE* hayperlaaaand) {\n    std::erase_if(m_decos, [&](const auto& other) { return other.get() == hayperlaaaand; });\n}\n\nvoid CServerDecorationKDEProtocol::createDecoration(COrgKdeKwinServerDecorationManager* pMgr, uint32_t id, wl_resource* surf) {\n    const auto CLIENT = pMgr->client();\n    const auto RESOURCE =\n        m_decos.emplace_back(makeUnique<CServerDecorationKDE>(makeShared<COrgKdeKwinServerDecoration>(CLIENT, pMgr->version(), id), CWLSurfaceResource::fromResource(surf))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_decos.pop_back();\n        return;\n    }\n}\n"
  },
  {
    "path": "src/protocols/ServerDecorationKDE.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"kde-server-decoration.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CServerDecorationKDE {\n  public:\n    CServerDecorationKDE(SP<COrgKdeKwinServerDecoration> resource_, SP<CWLSurfaceResource> surf);\n\n    SP<CWLSurfaceResource> m_surf;\n\n    uint32_t               m_mostRecentlySent      = 0;\n    uint32_t               m_mostRecentlyRequested = 0;\n\n    bool                   good();\n\n  private:\n    uint32_t                        kdeDefaultModeCSD();\n    uint32_t                        kdeModeOnRequestCSD(uint32_t modeRequestedByClient);\n    uint32_t                        kdeModeOnReleaseCSD();\n\n    SP<COrgKdeKwinServerDecoration> m_resource;\n\n    uint32_t                        m_requestsSent = 0;\n};\n\nclass CServerDecorationKDEProtocol : public IWaylandProtocol {\n  public:\n    CServerDecorationKDEProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    uint32_t kdeDefaultManagerModeCSD();\n\n    void     onManagerResourceDestroy(wl_resource* res);\n    void     destroyResource(CServerDecorationKDE* deco);\n\n    void     createDecoration(COrgKdeKwinServerDecorationManager* pMgr, uint32_t id, wl_resource* surf);\n\n    //\n    std::vector<UP<COrgKdeKwinServerDecorationManager>> m_managers;\n    std::vector<UP<CServerDecorationKDE>>               m_decos;\n\n    friend class CServerDecorationKDE;\n};\n\nnamespace PROTO {\n    inline UP<CServerDecorationKDEProtocol> serverDecorationKDE;\n};\n"
  },
  {
    "path": "src/protocols/SessionLock.cpp",
    "content": "#include \"SessionLock.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"FractionalScale.hpp\"\n#include \"LockNotify.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"core/Output.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../render/Renderer.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n\nCSessionLockSurface::CSessionLockSurface(SP<CExtSessionLockSurfaceV1> resource_, SP<CWLSurfaceResource> surface_, PHLMONITOR pMonitor_, WP<CSessionLock> owner_) :\n    m_resource(resource_), m_sessionLock(owner_), m_surface(surface_), m_monitor(pMonitor_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CExtSessionLockSurfaceV1* r) {\n        m_events.destroy.emit();\n        PROTO::sessionLock->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CExtSessionLockSurfaceV1* r) {\n        m_events.destroy.emit();\n        PROTO::sessionLock->destroyResource(this);\n    });\n\n    m_resource->setAckConfigure([this](CExtSessionLockSurfaceV1* r, uint32_t serial) { m_ackdConfigure = true; });\n\n    m_listeners.surfaceCommit = m_surface->m_events.commit.listen([this] {\n        if (!m_surface->m_current.texture) {\n            LOGM(Log::ERR, \"SessionLock attached a null buffer\");\n            m_resource->error(EXT_SESSION_LOCK_SURFACE_V1_ERROR_NULL_BUFFER, \"Null buffer attached\");\n            return;\n        }\n\n        if (!m_ackdConfigure) {\n            LOGM(Log::ERR, \"SessionLock committed without an ack\");\n            m_resource->error(EXT_SESSION_LOCK_SURFACE_V1_ERROR_COMMIT_BEFORE_FIRST_ACK, \"Committed surface before first ack\");\n            return;\n        }\n\n        if (m_committed)\n            m_events.commit.emit();\n        else {\n            m_surface->map();\n            m_events.map.emit();\n        }\n        m_committed = true;\n    });\n\n    m_listeners.surfaceDestroy = m_surface->m_events.destroy.listen([this] {\n        LOGM(Log::WARN, \"SessionLockSurface object remains but surface is being destroyed???\");\n        m_surface->unmap();\n        m_listeners.surfaceCommit.reset();\n        m_listeners.surfaceDestroy.reset();\n        if (Desktop::focusState()->surface() == m_surface)\n            Desktop::focusState()->surface().reset();\n\n        m_surface.reset();\n    });\n\n    if (m_monitor) {\n        PROTO::fractional->sendScale(surface_, m_monitor->m_scale);\n\n        if (m_surface)\n            m_surface->enter(m_monitor.lock());\n\n        m_listeners.monitorMode = m_monitor->m_events.modeChanged.listen([this] { sendConfigure(); });\n    }\n\n    sendConfigure();\n}\n\nCSessionLockSurface::~CSessionLockSurface() {\n    if (m_surface && m_surface->m_mapped)\n        m_surface->unmap();\n    m_listeners.surfaceCommit.reset();\n    m_listeners.surfaceDestroy.reset();\n    m_events.destroy.emit(); // just in case.\n}\n\nvoid CSessionLockSurface::sendConfigure() {\n    if (!m_monitor) {\n        LOGM(Log::ERR, \"sendConfigure: monitor is gone\");\n        return;\n    }\n\n    const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(m_resource->client()));\n    m_resource->sendConfigure(SERIAL, m_monitor->m_size.x, m_monitor->m_size.y);\n}\n\nbool CSessionLockSurface::good() {\n    return m_resource->resource();\n}\n\nbool CSessionLockSurface::inert() {\n    return m_sessionLock.expired();\n}\n\nPHLMONITOR CSessionLockSurface::monitor() {\n    return m_monitor.lock();\n}\n\nSP<CWLSurfaceResource> CSessionLockSurface::surface() {\n    return m_surface.lock();\n}\n\nCSessionLock::CSessionLock(SP<CExtSessionLockV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CExtSessionLockV1* r) { PROTO::sessionLock->destroyResource(this); });\n    m_resource->setOnDestroy([this](CExtSessionLockV1* r) { PROTO::sessionLock->destroyResource(this); });\n\n    m_resource->setGetLockSurface([this](CExtSessionLockV1* r, uint32_t id, wl_resource* surf, wl_resource* output) {\n        if (m_inert) {\n            LOGM(Log::ERR, \"Lock is trying to send getLockSurface after it's inert\");\n            return;\n        }\n\n        PROTO::sessionLock->onGetLockSurface(r, id, surf, output);\n    });\n\n    m_resource->setUnlockAndDestroy([this](CExtSessionLockV1* r) {\n        if (m_inert) {\n            PROTO::sessionLock->destroyResource(this);\n            return;\n        }\n\n        PROTO::sessionLock->m_locked = false;\n\n        PROTO::lockNotify->onUnlocked();\n\n        m_events.unlockAndDestroy.emit();\n\n        // if lock tools have hidden it and doesn't restore it, we won't receive a new cursor until the cursorshape protocol gives us one.\n        // so set it to left_ptr so the \"desktop/wallpaper\" doesn't end up missing a cursor until hover over a window sending us a shape.\n        g_pHyprRenderer->setCursorFromName(\"left_ptr\");\n\n        m_inert = true;\n        PROTO::sessionLock->destroyResource(this);\n    });\n}\n\nCSessionLock::~CSessionLock() {\n    m_events.destroyed.emit();\n}\n\nvoid CSessionLock::sendLocked() {\n    m_resource->sendLocked();\n    PROTO::lockNotify->onLocked();\n}\n\nbool CSessionLock::good() {\n    return m_resource->resource();\n}\n\nvoid CSessionLock::sendDenied() {\n    m_inert = true;\n    m_resource->sendFinished();\n}\n\nCSessionLockProtocol::CSessionLockProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CSessionLockProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CExtSessionLockManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CExtSessionLockManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CExtSessionLockManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setLock([this](CExtSessionLockManagerV1* pMgr, uint32_t id) { this->onLock(pMgr, id); });\n}\n\nvoid CSessionLockProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CSessionLockProtocol::destroyResource(CSessionLock* lock) {\n    std::erase_if(m_locks, [&](const auto& other) { return other.get() == lock; });\n}\n\nvoid CSessionLockProtocol::destroyResource(CSessionLockSurface* surf) {\n    std::erase_if(m_lockSurfaces, [&](const auto& other) { return other.get() == surf; });\n}\n\nvoid CSessionLockProtocol::onLock(CExtSessionLockManagerV1* pMgr, uint32_t id) {\n\n    LOGM(Log::DEBUG, \"New sessionLock with id {}\", id);\n\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_locks.emplace_back(makeShared<CSessionLock>(makeShared<CExtSessionLockV1>(CLIENT, pMgr->version(), id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_locks.pop_back();\n        return;\n    }\n\n    m_events.newLock.emit(RESOURCE);\n\n    m_locked = true;\n}\n\nvoid CSessionLockProtocol::onGetLockSurface(CExtSessionLockV1* lock, uint32_t id, wl_resource* surface, wl_resource* output) {\n    LOGM(Log::DEBUG, \"New sessionLockSurface with id {}\", id);\n\n    auto PSURFACE  = CWLSurfaceResource::fromResource(surface);\n    auto OUTPUTRES = CWLOutputResource::fromResource(output);\n    if (!OUTPUTRES) {\n        LOGM(Log::ERR, \"onGetLockSurface: invalid output resource\");\n        return;\n    }\n    auto PMONITOR = OUTPUTRES->m_monitor.lock();\n    if (!PMONITOR) {\n        LOGM(Log::ERR, \"onGetLockSurface: monitor is gone for output resource\");\n        return;\n    }\n\n    SP<CSessionLock> sessionLock;\n    for (auto const& l : m_locks) {\n        if (l->m_resource.get() == lock) {\n            sessionLock = l;\n            break;\n        }\n    }\n\n    const auto RESOURCE =\n        m_lockSurfaces.emplace_back(makeShared<CSessionLockSurface>(makeShared<CExtSessionLockSurfaceV1>(lock->client(), lock->version(), id), PSURFACE, PMONITOR, sessionLock));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        lock->noMemory();\n        m_lockSurfaces.pop_back();\n        return;\n    }\n\n    sessionLock->m_events.newLockSurface.emit(RESOURCE);\n}\n\nbool CSessionLockProtocol::isLocked() {\n    return m_locked;\n}\n"
  },
  {
    "path": "src/protocols/SessionLock.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"ext-session-lock-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CMonitor;\nclass CSessionLock;\nclass CWLSurfaceResource;\n\nclass CSessionLockSurface {\n  public:\n    CSessionLockSurface(SP<CExtSessionLockSurfaceV1> resource_, SP<CWLSurfaceResource> surface_, PHLMONITOR pMonitor_, WP<CSessionLock> owner_);\n    ~CSessionLockSurface();\n\n    bool                   good();\n    bool                   inert();\n    PHLMONITOR             monitor();\n    SP<CWLSurfaceResource> surface();\n\n    struct {\n        CSignalT<> map;\n        CSignalT<> destroy;\n        CSignalT<> commit;\n    } m_events;\n\n  private:\n    SP<CExtSessionLockSurfaceV1> m_resource;\n    WP<CSessionLock>             m_sessionLock;\n    WP<CWLSurfaceResource>       m_surface;\n    PHLMONITORREF                m_monitor;\n\n    bool                         m_ackdConfigure = false;\n    bool                         m_committed     = false;\n\n    void                         sendConfigure();\n\n    struct {\n        CHyprSignalListener monitorMode;\n        CHyprSignalListener surfaceCommit;\n        CHyprSignalListener surfaceDestroy;\n    } m_listeners;\n};\n\nclass CSessionLock {\n  public:\n    CSessionLock(SP<CExtSessionLockV1> resource_);\n    ~CSessionLock();\n\n    bool good();\n    void sendLocked();\n    void sendDenied();\n\n    struct {\n        CSignalT<SP<CSessionLockSurface>> newLockSurface;\n        CSignalT<>                        unlockAndDestroy;\n        CSignalT<>                        destroyed; // fires regardless of whether there was a unlockAndDestroy or not.\n    } m_events;\n\n  private:\n    SP<CExtSessionLockV1> m_resource;\n\n    bool                  m_inert = false;\n\n    friend class CSessionLockProtocol;\n};\n\nclass CSessionLockProtocol : public IWaylandProtocol {\n  public:\n    CSessionLockProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    bool         isLocked();\n\n    struct {\n        CSignalT<SP<CSessionLock>> newLock;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CSessionLock* lock);\n    void destroyResource(CSessionLockSurface* surf);\n    void onLock(CExtSessionLockManagerV1* pMgr, uint32_t id);\n    void onGetLockSurface(CExtSessionLockV1* lock, uint32_t id, wl_resource* surface, wl_resource* output);\n\n    bool m_locked = false;\n\n    //\n    std::vector<UP<CExtSessionLockManagerV1>> m_managers;\n    std::vector<SP<CSessionLock>>             m_locks;\n    std::vector<SP<CSessionLockSurface>>      m_lockSurfaces;\n\n    friend class CSessionLock;\n    friend class CSessionLockSurface;\n};\n\nnamespace PROTO {\n    inline UP<CSessionLockProtocol> sessionLock;\n};\n"
  },
  {
    "path": "src/protocols/ShortcutsInhibit.cpp",
    "content": "#include \"ShortcutsInhibit.hpp\"\n#include <algorithm>\n#include \"../Compositor.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"core/Compositor.hpp\"\n\nCKeyboardShortcutsInhibitor::CKeyboardShortcutsInhibitor(SP<CZwpKeyboardShortcutsInhibitorV1> resource_, SP<CWLSurfaceResource> surf) : m_resource(resource_), m_surface(surf) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CZwpKeyboardShortcutsInhibitorV1* pMgr) { PROTO::shortcutsInhibit->destroyInhibitor(this); });\n    m_resource->setOnDestroy([this](CZwpKeyboardShortcutsInhibitorV1* pMgr) { PROTO::shortcutsInhibit->destroyInhibitor(this); });\n\n    // I don't really care about following the spec here that much,\n    // let's make the app believe it's always active\n    m_resource->sendActive();\n}\n\nSP<CWLSurfaceResource> CKeyboardShortcutsInhibitor::surface() {\n    return m_surface.lock();\n}\n\nbool CKeyboardShortcutsInhibitor::good() {\n    return m_resource->resource();\n}\n\nCKeyboardShortcutsInhibitProtocol::CKeyboardShortcutsInhibitProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CKeyboardShortcutsInhibitProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpKeyboardShortcutsInhibitManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpKeyboardShortcutsInhibitManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpKeyboardShortcutsInhibitManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setInhibitShortcuts(\n        [this](CZwpKeyboardShortcutsInhibitManagerV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* seat) { this->onInhibit(pMgr, id, surface, seat); });\n}\n\nvoid CKeyboardShortcutsInhibitProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CKeyboardShortcutsInhibitProtocol::destroyInhibitor(CKeyboardShortcutsInhibitor* inhibitor) {\n    std::erase_if(m_inhibitors, [&](const auto& other) { return other.get() == inhibitor; });\n}\n\nvoid CKeyboardShortcutsInhibitProtocol::onInhibit(CZwpKeyboardShortcutsInhibitManagerV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* seat) {\n    SP<CWLSurfaceResource> surf   = CWLSurfaceResource::fromResource(surface);\n    const auto             CLIENT = pMgr->client();\n\n    for (auto const& in : m_inhibitors) {\n        if LIKELY (in->surface() != surf)\n            continue;\n\n        pMgr->error(ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED, \"Already inhibited for surface resource\");\n        return;\n    }\n\n    const auto RESOURCE = m_inhibitors.emplace_back(makeUnique<CKeyboardShortcutsInhibitor>(makeShared<CZwpKeyboardShortcutsInhibitorV1>(CLIENT, pMgr->version(), id), surf)).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_inhibitors.pop_back();\n        LOGM(Log::ERR, \"Failed to create an inhibitor resource\");\n        return;\n    }\n}\n\nbool CKeyboardShortcutsInhibitProtocol::isInhibited() {\n    if (!Desktop::focusState()->surface())\n        return false;\n\n    if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(Desktop::focusState()->surface()); PWINDOW && PWINDOW->m_ruleApplicator->noShortcutsInhibit().valueOrDefault())\n        return false;\n\n    for (auto const& in : m_inhibitors) {\n        if (in->surface() != Desktop::focusState()->surface())\n            continue;\n\n        return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/protocols/ShortcutsInhibit.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"keyboard-shortcuts-inhibit-unstable-v1.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CKeyboardShortcutsInhibitor {\n  public:\n    CKeyboardShortcutsInhibitor(SP<CZwpKeyboardShortcutsInhibitorV1> resource_, SP<CWLSurfaceResource> surf);\n\n    // read-only pointer, may be invalid\n    SP<CWLSurfaceResource> surface();\n    bool                   good();\n\n  private:\n    SP<CZwpKeyboardShortcutsInhibitorV1> m_resource;\n    WP<CWLSurfaceResource>               m_surface;\n};\n\nclass CKeyboardShortcutsInhibitProtocol : public IWaylandProtocol {\n  public:\n    CKeyboardShortcutsInhibitProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    bool         isInhibited();\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyInhibitor(CKeyboardShortcutsInhibitor* pointer);\n    void onInhibit(CZwpKeyboardShortcutsInhibitManagerV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* seat);\n\n    //\n    std::vector<UP<CZwpKeyboardShortcutsInhibitManagerV1>> m_managers;\n    std::vector<UP<CKeyboardShortcutsInhibitor>>           m_inhibitors;\n\n    friend class CKeyboardShortcutsInhibitor;\n};\n\nnamespace PROTO {\n    inline UP<CKeyboardShortcutsInhibitProtocol> shortcutsInhibit;\n};"
  },
  {
    "path": "src/protocols/SinglePixel.cpp",
    "content": "#include \"SinglePixel.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include <limits>\n#include \"render/Renderer.hpp\"\n\nCSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColor col_) {\n    LOGM(Log::DEBUG, \"New single-pixel buffer with color 0x{:x}\", col_.getAsHex());\n\n    m_color = col_.getAsHex();\n\n    m_opaque = col_.a >= 1.F;\n\n    m_texture = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc<uint8_t*>(&m_color), 4, Vector2D{1, 1});\n\n    m_resource = CWLBufferResource::create(makeShared<CWlBuffer>(client, 1, id));\n\n    m_success = m_texture->ok();\n\n    size = {1, 1};\n\n    if (!m_success)\n        Log::logger->log(Log::ERR, \"Failed creating a single pixel texture: null texture id\");\n}\n\nCSinglePixelBuffer::~CSinglePixelBuffer() {\n    if (m_resource)\n        m_resource->sendRelease();\n}\n\nAquamarine::eBufferCapability CSinglePixelBuffer::caps() {\n    return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;\n}\n\nAquamarine::eBufferType CSinglePixelBuffer::type() {\n    return Aquamarine::eBufferType::BUFFER_TYPE_SHM;\n}\n\nbool CSinglePixelBuffer::isSynchronous() {\n    return true;\n}\n\nvoid CSinglePixelBuffer::update(const CRegion& damage) {\n    ;\n}\n\nAquamarine::SDMABUFAttrs CSinglePixelBuffer::dmabuf() {\n    return {.success = false};\n}\n\nstd::tuple<uint8_t*, uint32_t, size_t> CSinglePixelBuffer::beginDataPtr(uint32_t flags) {\n    return {rc<uint8_t*>(&m_color), DRM_FORMAT_ARGB8888, 4};\n}\n\nvoid CSinglePixelBuffer::endDataPtr() {\n    ;\n}\n\nbool CSinglePixelBuffer::good() {\n    return m_resource->good();\n}\n\nCSinglePixelBufferResource::CSinglePixelBufferResource(uint32_t id, wl_client* client, CHyprColor color) {\n    m_buffer = makeShared<CSinglePixelBuffer>(id, client, color);\n\n    if UNLIKELY (!m_buffer->good())\n        return;\n\n    m_buffer->m_resource->m_buffer = m_buffer;\n\n    m_listeners.bufferResourceDestroy = m_buffer->events.destroy.listen([this] {\n        m_listeners.bufferResourceDestroy.reset();\n        PROTO::singlePixel->destroyResource(this);\n    });\n}\n\nbool CSinglePixelBufferResource::good() {\n    return m_buffer->good();\n}\n\nCSinglePixelBufferManagerResource::CSinglePixelBufferManagerResource(UP<CWpSinglePixelBufferManagerV1>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWpSinglePixelBufferManagerV1* r) { PROTO::singlePixel->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpSinglePixelBufferManagerV1* r) { PROTO::singlePixel->destroyResource(this); });\n\n    m_resource->setCreateU32RgbaBuffer([this](CWpSinglePixelBufferManagerV1* res, uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) {\n        CHyprColor  color{r / sc<float>(std::numeric_limits<uint32_t>::max()), g / sc<float>(std::numeric_limits<uint32_t>::max()),\n                          b / sc<float>(std::numeric_limits<uint32_t>::max()), a / sc<float>(std::numeric_limits<uint32_t>::max())};\n        const auto& RESOURCE = PROTO::singlePixel->m_buffers.emplace_back(makeUnique<CSinglePixelBufferResource>(id, m_resource->client(), color));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            res->noMemory();\n            PROTO::singlePixel->m_buffers.pop_back();\n            return;\n        }\n    });\n}\n\nbool CSinglePixelBufferManagerResource::good() {\n    return m_resource->resource();\n}\n\nCSinglePixelProtocol::CSinglePixelProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CSinglePixelProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto& RESOURCE = m_managers.emplace_back(makeUnique<CSinglePixelBufferManagerResource>(makeUnique<CWpSinglePixelBufferManagerV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CSinglePixelProtocol::destroyResource(CSinglePixelBufferManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n\nvoid CSinglePixelProtocol::destroyResource(CSinglePixelBufferResource* surf) {\n    std::erase_if(m_buffers, [&](const auto& other) { return other.get() == surf; });\n}\n"
  },
  {
    "path": "src/protocols/SinglePixel.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"single-pixel-buffer-v1.hpp\"\n#include \"types/Buffer.hpp\"\n\nclass CSinglePixelBuffer : public IHLBuffer {\n  public:\n    CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColor col);\n    virtual ~CSinglePixelBuffer();\n\n    virtual Aquamarine::eBufferCapability          caps();\n    virtual Aquamarine::eBufferType                type();\n    virtual bool                                   isSynchronous();\n    virtual void                                   update(const CRegion& damage);\n    virtual Aquamarine::SDMABUFAttrs               dmabuf();\n    virtual std::tuple<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags);\n    virtual void                                   endDataPtr();\n    //\n    bool good();\n    bool m_success = false;\n\n  private:\n    uint32_t m_color = 0x00000000;\n};\n\nclass CSinglePixelBufferResource {\n  public:\n    CSinglePixelBufferResource(uint32_t id, wl_client* client, CHyprColor color);\n    ~CSinglePixelBufferResource() = default;\n\n    bool good();\n\n  private:\n    SP<CSinglePixelBuffer> m_buffer;\n\n    struct {\n        CHyprSignalListener bufferResourceDestroy;\n    } m_listeners;\n};\n\nclass CSinglePixelBufferManagerResource {\n  public:\n    CSinglePixelBufferManagerResource(UP<CWpSinglePixelBufferManagerV1>&& resource_);\n\n    bool good();\n\n  private:\n    UP<CWpSinglePixelBufferManagerV1> m_resource;\n};\n\nclass CSinglePixelProtocol : public IWaylandProtocol {\n  public:\n    CSinglePixelProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CSinglePixelBufferManagerResource* resource);\n    void destroyResource(CSinglePixelBufferResource* resource);\n\n    //\n    std::vector<UP<CSinglePixelBufferManagerResource>> m_managers;\n    std::vector<UP<CSinglePixelBufferResource>>        m_buffers;\n\n    friend class CSinglePixelBufferManagerResource;\n    friend class CSinglePixelBufferResource;\n};\n\nnamespace PROTO {\n    inline UP<CSinglePixelProtocol> singlePixel;\n};\n"
  },
  {
    "path": "src/protocols/Tablet.cpp",
    "content": "#include \"Tablet.hpp\"\n#include \"../devices/Tablet.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../helpers/time/Time.hpp\"\n#include \"core/Seat.hpp\"\n#include \"core/Compositor.hpp\"\n#include <algorithm>\n#include <cstring>\n\nCTabletPadStripV2Resource::CTabletPadStripV2Resource(SP<CZwpTabletPadStripV2> resource_, uint32_t id_) : m_id(id_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletPadStripV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletPadStripV2* r) { PROTO::tablet->destroyResource(this); });\n}\n\nbool CTabletPadStripV2Resource::good() {\n    return m_resource->resource();\n}\n\nCTabletPadRingV2Resource::CTabletPadRingV2Resource(SP<CZwpTabletPadRingV2> resource_, uint32_t id_) : m_id(id_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletPadRingV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletPadRingV2* r) { PROTO::tablet->destroyResource(this); });\n}\n\nbool CTabletPadRingV2Resource::good() {\n    return m_resource->resource();\n}\n\nCTabletPadGroupV2Resource::CTabletPadGroupV2Resource(SP<CZwpTabletPadGroupV2> resource_, size_t idx_) : m_idx(idx_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletPadGroupV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletPadGroupV2* r) { PROTO::tablet->destroyResource(this); });\n}\n\nbool CTabletPadGroupV2Resource::good() {\n    return m_resource->resource();\n}\n\nvoid CTabletPadGroupV2Resource::sendData(SP<CTabletPad> pad, SP<Aquamarine::ITabletPad::STabletPadGroup> group) {\n    m_resource->sendModes(group->modes);\n\n    wl_array buttonArr;\n    wl_array_init(&buttonArr);\n    wl_array_add(&buttonArr, group->buttons.size() * sizeof(int));\n    memcpy(buttonArr.data, group->buttons.data(), group->buttons.size() * sizeof(int));\n    m_resource->sendButtons(&buttonArr);\n    wl_array_release(&buttonArr);\n\n    for (size_t i = 0; i < group->strips.size(); ++i) {\n        const auto RESOURCE =\n            PROTO::tablet->m_strips.emplace_back(makeShared<CTabletPadStripV2Resource>(makeShared<CZwpTabletPadStripV2>(m_resource->client(), m_resource->version(), 0), i));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            m_resource->noMemory();\n            PROTO::tablet->m_strips.pop_back();\n            return;\n        }\n\n        m_resource->sendStrip(RESOURCE->m_resource.get());\n    }\n\n    for (size_t i = 0; i < group->rings.size(); ++i) {\n        const auto RESOURCE =\n            PROTO::tablet->m_rings.emplace_back(makeShared<CTabletPadRingV2Resource>(makeShared<CZwpTabletPadRingV2>(m_resource->client(), m_resource->version(), 0), i));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            m_resource->noMemory();\n            PROTO::tablet->m_rings.pop_back();\n            return;\n        }\n\n        m_resource->sendRing(RESOURCE->m_resource.get());\n    }\n\n    m_resource->sendDone();\n}\n\nCTabletPadV2Resource::CTabletPadV2Resource(SP<CZwpTabletPadV2> resource_, SP<CTabletPad> pad_, SP<CTabletSeat> seat_) : m_pad(pad_), m_seat(seat_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletPadV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletPadV2* r) { PROTO::tablet->destroyResource(this); });\n}\n\nbool CTabletPadV2Resource::good() {\n    return m_resource->resource();\n}\n\nvoid CTabletPadV2Resource::sendData() {\n    for (auto const& p : m_pad->aq()->paths) {\n        m_resource->sendPath(p.c_str());\n    }\n\n    m_resource->sendButtons(m_pad->aq()->buttons);\n\n    for (size_t i = 0; i < m_pad->aq()->groups.size(); ++i) {\n        createGroup(m_pad->aq()->groups.at(i), i);\n    }\n\n    m_resource->sendDone();\n}\n\nvoid CTabletPadV2Resource::createGroup(SP<Aquamarine::ITabletPad::STabletPadGroup> group, size_t idx) {\n    const auto RESOURCE =\n        PROTO::tablet->m_groups.emplace_back(makeShared<CTabletPadGroupV2Resource>(makeShared<CZwpTabletPadGroupV2>(m_resource->client(), m_resource->version(), 0), idx));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        m_resource->noMemory();\n        PROTO::tablet->m_groups.pop_back();\n        return;\n    }\n\n    m_resource->sendGroup(RESOURCE->m_resource.get());\n\n    RESOURCE->sendData(m_pad.lock(), group);\n}\n\nCTabletV2Resource::CTabletV2Resource(SP<CZwpTabletV2> resource_, SP<CTablet> tablet_, SP<CTabletSeat> seat_) : m_tablet(tablet_), m_seat(seat_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletV2* r) { PROTO::tablet->destroyResource(this); });\n}\n\nbool CTabletV2Resource::good() {\n    return m_resource->resource();\n}\n\nvoid CTabletV2Resource::sendData() {\n    m_resource->sendName(m_tablet->m_deviceName.c_str());\n    m_resource->sendId(m_tablet->aq()->usbVendorID, m_tablet->aq()->usbProductID);\n\n    for (auto const& p : m_tablet->aq()->paths) {\n        m_resource->sendPath(p.c_str());\n    }\n\n    m_resource->sendDone();\n}\n\nCTabletToolV2Resource::CTabletToolV2Resource(SP<CZwpTabletToolV2> resource_, SP<CTabletTool> tool_, SP<CTabletSeat> seat_) : m_tool(tool_), m_seat(seat_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletToolV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletToolV2* r) { PROTO::tablet->destroyResource(this); });\n\n    m_resource->setSetCursor([](CZwpTabletToolV2* r, uint32_t serial, wl_resource* surf, int32_t hot_x, int32_t hot_y) {\n        if (!g_pSeatManager->m_state.pointerFocusResource || g_pSeatManager->m_state.pointerFocusResource->client() != r->client())\n            return;\n\n        g_pInputManager->processMouseRequest(CSeatManager::SSetCursorEvent{surf ? CWLSurfaceResource::fromResource(surf) : nullptr, {hot_x, hot_y}});\n    });\n}\n\nCTabletToolV2Resource::~CTabletToolV2Resource() {\n    if (m_frameSource)\n        wl_event_source_remove(m_frameSource);\n}\n\nbool CTabletToolV2Resource::good() {\n    return m_resource->resource();\n}\n\nvoid CTabletToolV2Resource::sendData() {\n    static auto AQ_TYPE_TO_PROTO = [](uint32_t aq) -> zwpTabletToolV2Type {\n        switch (aq) {\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_PEN: return ZWP_TABLET_TOOL_V2_TYPE_PEN;\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_ERASER: return ZWP_TABLET_TOOL_V2_TYPE_ERASER;\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_BRUSH: return ZWP_TABLET_TOOL_V2_TYPE_BRUSH;\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_PENCIL: return ZWP_TABLET_TOOL_V2_TYPE_PENCIL;\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_AIRBRUSH: return ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH;\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_MOUSE: return ZWP_TABLET_TOOL_V2_TYPE_MOUSE;\n            case Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_LENS: return ZWP_TABLET_TOOL_V2_TYPE_LENS;\n            default: ASSERT(false);\n        }\n        UNREACHABLE();\n    };\n\n    m_resource->sendType(AQ_TYPE_TO_PROTO(m_tool->aq()->type));\n    m_resource->sendHardwareSerial(m_tool->aq()->serial >> 32, m_tool->aq()->serial & 0xFFFFFFFF);\n    m_resource->sendHardwareIdWacom(m_tool->aq()->id >> 32, m_tool->aq()->id & 0xFFFFFFFF);\n    if (m_tool->m_toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_DISTANCE)\n        m_resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE);\n    if (m_tool->m_toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_PRESSURE)\n        m_resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE);\n    if (m_tool->m_toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_ROTATION)\n        m_resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION);\n    if (m_tool->m_toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_SLIDER)\n        m_resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER);\n    if (m_tool->m_toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_TILT)\n        m_resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_TILT);\n    if (m_tool->m_toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_WHEEL)\n        m_resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL);\n    m_resource->sendDone();\n}\n\nvoid CTabletToolV2Resource::queueFrame() {\n    if (m_frameSource)\n        return;\n\n    m_frameSource = wl_event_loop_add_idle(g_pCompositor->m_wlEventLoop, [](void* data) { sc<CTabletToolV2Resource*>(data)->sendFrame(false); }, this);\n}\n\nvoid CTabletToolV2Resource::sendFrame(bool removeSource) {\n    if (m_frameSource) {\n        if (removeSource)\n            wl_event_source_remove(m_frameSource);\n        m_frameSource = nullptr;\n    }\n\n    if (!m_current)\n        return;\n\n    m_resource->sendFrame(Time::millis(Time::steadyNow()));\n}\n\nCTabletSeat::CTabletSeat(SP<CZwpTabletSeatV2> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpTabletSeatV2* r) { PROTO::tablet->destroyResource(this); });\n    m_resource->setOnDestroy([this](CZwpTabletSeatV2* r) { PROTO::tablet->destroyResource(this); });\n}\n\nbool CTabletSeat::good() {\n    return m_resource->resource();\n}\n\nvoid CTabletSeat::sendTool(SP<CTabletTool> tool) {\n    const auto RESOURCE =\n        PROTO::tablet->m_tools.emplace_back(makeShared<CTabletToolV2Resource>(makeShared<CZwpTabletToolV2>(m_resource->client(), m_resource->version(), 0), tool, m_self.lock()));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        m_resource->noMemory();\n        PROTO::tablet->m_tools.pop_back();\n        return;\n    }\n\n    m_resource->sendToolAdded(RESOURCE->m_resource.get());\n\n    RESOURCE->sendData();\n    m_tools.emplace_back(RESOURCE);\n}\n\nvoid CTabletSeat::sendPad(SP<CTabletPad> pad) {\n    const auto RESOURCE =\n        PROTO::tablet->m_pads.emplace_back(makeShared<CTabletPadV2Resource>(makeShared<CZwpTabletPadV2>(m_resource->client(), m_resource->version(), 0), pad, m_self.lock()));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        m_resource->noMemory();\n        PROTO::tablet->m_pads.pop_back();\n        return;\n    }\n\n    m_resource->sendPadAdded(RESOURCE->m_resource.get());\n\n    RESOURCE->sendData();\n    m_pads.emplace_back(RESOURCE);\n}\n\nvoid CTabletSeat::sendTablet(SP<CTablet> tablet) {\n    const auto RESOURCE =\n        PROTO::tablet->m_tablets.emplace_back(makeShared<CTabletV2Resource>(makeShared<CZwpTabletV2>(m_resource->client(), m_resource->version(), 0), tablet, m_self.lock()));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        m_resource->noMemory();\n        PROTO::tablet->m_tablets.pop_back();\n        return;\n    }\n\n    m_resource->sendTabletAdded(RESOURCE->m_resource.get());\n\n    RESOURCE->sendData();\n    m_tablets.emplace_back(RESOURCE);\n}\n\nvoid CTabletSeat::sendData() {\n    for (auto const& tw : PROTO::tablet->m_tabletDevices) {\n        if (tw.expired())\n            continue;\n\n        sendTablet(tw.lock());\n    }\n\n    for (auto const& tw : PROTO::tablet->m_toolDevices) {\n        if (tw.expired())\n            continue;\n\n        sendTool(tw.lock());\n    }\n\n    for (auto const& tw : PROTO::tablet->m_padDevices) {\n        if (tw.expired())\n            continue;\n\n        sendPad(tw.lock());\n    }\n}\n\nCTabletV2Protocol::CTabletV2Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CTabletV2Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpTabletManagerV2>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpTabletManagerV2* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpTabletManagerV2* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetTabletSeat([this](CZwpTabletManagerV2* pMgr, uint32_t id, wl_resource* seat) { this->onGetSeat(pMgr, id, seat); });\n}\n\nvoid CTabletV2Protocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletSeat* resource) {\n    std::erase_if(m_seats, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletToolV2Resource* resource) {\n    std::erase_if(m_tools, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletV2Resource* resource) {\n    std::erase_if(m_tablets, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletPadV2Resource* resource) {\n    std::erase_if(m_pads, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletPadGroupV2Resource* resource) {\n    std::erase_if(m_groups, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletPadRingV2Resource* resource) {\n    std::erase_if(m_rings, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::destroyResource(CTabletPadStripV2Resource* resource) {\n    std::erase_if(m_strips, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CTabletV2Protocol::onGetSeat(CZwpTabletManagerV2* pMgr, uint32_t id, wl_resource* seat) {\n    const auto RESOURCE = m_seats.emplace_back(makeShared<CTabletSeat>(makeShared<CZwpTabletSeatV2>(pMgr->client(), pMgr->version(), id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_seats.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self = RESOURCE;\n    RESOURCE->sendData();\n}\n\nvoid CTabletV2Protocol::registerDevice(SP<CTablet> tablet) {\n    for (auto const& s : m_seats) {\n        s->sendTablet(tablet);\n    }\n\n    m_tabletDevices.emplace_back(tablet);\n}\n\nvoid CTabletV2Protocol::registerDevice(SP<CTabletTool> tool) {\n    for (auto const& s : m_seats) {\n        s->sendTool(tool);\n    }\n\n    m_toolDevices.emplace_back(tool);\n}\n\nvoid CTabletV2Protocol::registerDevice(SP<CTabletPad> pad) {\n    for (auto const& s : m_seats) {\n        s->sendPad(pad);\n    }\n\n    m_padDevices.emplace_back(pad);\n}\n\nvoid CTabletV2Protocol::unregisterDevice(SP<CTablet> tablet) {\n    for (auto const& t : m_tablets) {\n        if (t->m_tablet == tablet) {\n            t->m_resource->sendRemoved();\n            t->m_inert = true;\n        }\n    }\n    std::erase_if(m_tabletDevices, [tablet](const auto& e) { return e.expired() || e == tablet; });\n}\n\nvoid CTabletV2Protocol::unregisterDevice(SP<CTabletTool> tool) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool == tool) {\n            t->m_resource->sendRemoved();\n            t->m_inert = true;\n        }\n    }\n    std::erase_if(m_toolDevices, [tool](const auto& e) { return e.expired() || e == tool; });\n}\n\nvoid CTabletV2Protocol::unregisterDevice(SP<CTabletPad> pad) {\n    for (auto const& t : m_pads) {\n        if (t->m_pad == pad) {\n            t->m_resource->sendRemoved();\n            t->m_inert = true;\n        }\n    }\n    std::erase_if(m_padDevices, [pad](const auto& e) { return e.expired() || e == pad; });\n}\n\nvoid CTabletV2Protocol::recheckRegisteredDevices() {\n    std::erase_if(m_tabletDevices, [](const auto& e) { return e.expired(); });\n    std::erase_if(m_toolDevices, [](const auto& e) { return e.expired(); });\n    std::erase_if(m_padDevices, [](const auto& e) { return e.expired(); });\n\n    // now we need to send removed events\n    for (auto const& t : m_tablets) {\n        if (!t->m_tablet.expired() || t->m_inert)\n            continue;\n\n        t->m_resource->sendRemoved();\n        t->m_inert = true;\n    }\n\n    for (auto const& t : m_tools) {\n        if (!t->m_tool.expired() || t->m_inert)\n            continue;\n\n        if (t->m_current) {\n            t->m_resource->sendProximityOut();\n            t->sendFrame();\n            t->m_lastSurf.reset();\n        }\n\n        t->m_resource->sendRemoved();\n        t->m_inert = true;\n    }\n\n    for (auto const& t : m_pads) {\n        if (!t->m_pad.expired() || t->m_inert)\n            continue;\n\n        t->m_resource->sendRemoved();\n        t->m_inert = true;\n    }\n}\n\nvoid CTabletV2Protocol::pressure(SP<CTabletTool> tool, double value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendPressure(std::clamp(value * 65535, 0.0, 65535.0));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::distance(SP<CTabletTool> tool, double value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendDistance(std::clamp(value * 65535, 0.0, 65535.0));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::rotation(SP<CTabletTool> tool, double value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendRotation(wl_fixed_from_double(value));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::slider(SP<CTabletTool> tool, double value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendSlider(std::clamp(value * 65535, -65535.0, 65535.0));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::wheel(SP<CTabletTool> tool, double value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendWheel(wl_fixed_from_double(value), 0);\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::tilt(SP<CTabletTool> tool, const Vector2D& value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendTilt(wl_fixed_from_double(value.x), wl_fixed_from_double(value.y));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::up(SP<CTabletTool> tool) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendUp();\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::down(SP<CTabletTool> tool) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        auto serial = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(t->m_resource->client()));\n        t->m_resource->sendDown(serial);\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::proximityIn(SP<CTabletTool> tool, SP<CTablet> tablet, SP<CWLSurfaceResource> surf) {\n    proximityOut(tool);\n    const auto                CLIENT = surf->client();\n\n    SP<CTabletToolV2Resource> toolResource;\n    SP<CTabletV2Resource>     tabletResource;\n\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || t->m_resource->client() != CLIENT)\n            continue;\n\n        if (t->m_seat.expired()) {\n            LOGM(Log::ERR, \"proximityIn on a tool without a seat parent\");\n            return;\n        }\n\n        if (t->m_lastSurf == surf)\n            return;\n\n        toolResource = t;\n\n        for (auto const& tab : m_tablets) {\n            if (tab->m_tablet != tablet)\n                continue;\n\n            if (tab->m_seat != t->m_seat || !tab->m_seat)\n                continue;\n\n            tabletResource = tab;\n            break;\n        }\n    }\n\n    if (!tabletResource || !toolResource) {\n        LOGM(Log::ERR, \"proximityIn on a tool and tablet without valid resource(s)??\");\n        return;\n    }\n\n    toolResource->m_current  = true;\n    toolResource->m_lastSurf = surf;\n\n    auto serial = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(toolResource->m_resource->client()));\n    toolResource->m_resource->sendProximityIn(serial, tabletResource->m_resource.get(), surf->getResource()->resource());\n    toolResource->queueFrame();\n\n    LOGM(Log::ERR, \"proximityIn: found no resource to send enter\");\n}\n\nvoid CTabletV2Protocol::proximityOut(SP<CTabletTool> tool) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_lastSurf.reset();\n        t->m_resource->sendProximityOut();\n        t->sendFrame();\n        t->m_current = false;\n    }\n}\n\nvoid CTabletV2Protocol::buttonTool(SP<CTabletTool> tool, uint32_t button, uint32_t state) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        auto serial = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(t->m_resource->client()));\n        t->m_resource->sendButton(serial, button, sc<zwpTabletToolV2ButtonState>(state));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::motion(SP<CTabletTool> tool, const Vector2D& value) {\n    for (auto const& t : m_tools) {\n        if (t->m_tool != tool || !t->m_current)\n            continue;\n\n        t->m_resource->sendMotion(wl_fixed_from_double(value.x), wl_fixed_from_double(value.y));\n        t->queueFrame();\n    }\n}\n\nvoid CTabletV2Protocol::mode(SP<CTabletPad> pad, uint32_t group, uint32_t mode, uint32_t timeMs) {\n    for (auto const& t : m_pads) {\n        if (t->m_pad != pad)\n            continue;\n        if (t->m_groups.size() <= group) {\n            LOGM(Log::ERR, \"BUG THIS: group >= t->groups.size()\");\n            return;\n        }\n        auto serial = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(t->m_resource->client()));\n        t->m_groups.at(group)->m_resource->sendModeSwitch(timeMs, serial, mode);\n    }\n}\n\nvoid CTabletV2Protocol::buttonPad(SP<CTabletPad> pad, uint32_t button, uint32_t timeMs, uint32_t state) {\n    for (auto const& t : m_pads) {\n        if (t->m_pad != pad)\n            continue;\n        t->m_resource->sendButton(timeMs, button, zwpTabletToolV2ButtonState{state});\n    }\n}\n\nvoid CTabletV2Protocol::strip(SP<CTabletPad> pad, uint32_t strip, double position, bool finger, uint32_t timeMs) {\n    LOGM(Log::ERR, \"FIXME: STUB: CTabletV2Protocol::strip not implemented\");\n}\n\nvoid CTabletV2Protocol::ring(SP<CTabletPad> pad, uint32_t ring, double position, bool finger, uint32_t timeMs) {\n    LOGM(Log::ERR, \"FIXME: STUB: CTabletV2Protocol::ring not implemented\");\n}\n"
  },
  {
    "path": "src/protocols/Tablet.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"tablet-v2.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include <aquamarine/input/Input.hpp>\n\nclass CTablet;\nclass CTabletTool;\nclass CTabletPad;\nclass CEventLoopTimer;\nclass CTabletSeat;\nclass CWLSurfaceResource;\n\nclass CTabletPadStripV2Resource {\n  public:\n    CTabletPadStripV2Resource(SP<CZwpTabletPadStripV2> resource_, uint32_t id);\n\n    bool     good();\n\n    uint32_t m_id = 0;\n\n  private:\n    SP<CZwpTabletPadStripV2> m_resource;\n\n    friend class CTabletSeat;\n    friend class CTabletPadGroupV2Resource;\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletPadRingV2Resource {\n  public:\n    CTabletPadRingV2Resource(SP<CZwpTabletPadRingV2> resource_, uint32_t id);\n\n    bool     good();\n\n    uint32_t m_id = 0;\n\n  private:\n    SP<CZwpTabletPadRingV2> m_resource;\n\n    friend class CTabletSeat;\n    friend class CTabletPadGroupV2Resource;\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletPadGroupV2Resource {\n  public:\n    CTabletPadGroupV2Resource(SP<CZwpTabletPadGroupV2> resource_, size_t idx);\n\n    bool   good();\n    void   sendData(SP<CTabletPad> pad, SP<Aquamarine::ITabletPad::STabletPadGroup> group);\n\n    size_t m_idx = 0;\n\n  private:\n    SP<CZwpTabletPadGroupV2> m_resource;\n\n    friend class CTabletSeat;\n    friend class CTabletPadV2Resource;\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletPadV2Resource {\n  public:\n    CTabletPadV2Resource(SP<CZwpTabletPadV2> resource_, SP<CTabletPad> pad_, SP<CTabletSeat> seat_);\n\n    bool                                       good();\n    void                                       sendData();\n\n    std::vector<WP<CTabletPadGroupV2Resource>> m_groups;\n\n    WP<CTabletPad>                             m_pad;\n    WP<CTabletSeat>                            m_seat;\n\n    bool                                       m_inert = false; // removed was sent\n\n  private:\n    SP<CZwpTabletPadV2> m_resource;\n\n    void                createGroup(SP<Aquamarine::ITabletPad::STabletPadGroup> group, size_t idx);\n\n    friend class CTabletSeat;\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletV2Resource {\n  public:\n    CTabletV2Resource(SP<CZwpTabletV2> resource_, SP<CTablet> tablet_, SP<CTabletSeat> seat_);\n\n    bool            good();\n    void            sendData();\n\n    WP<CTablet>     m_tablet;\n    WP<CTabletSeat> m_seat;\n\n    bool            m_inert = false; // removed was sent\n\n  private:\n    SP<CZwpTabletV2> m_resource;\n\n    friend class CTabletSeat;\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletToolV2Resource {\n  public:\n    CTabletToolV2Resource(SP<CZwpTabletToolV2> resource_, SP<CTabletTool> tool_, SP<CTabletSeat> seat_);\n    ~CTabletToolV2Resource();\n\n    bool                   good();\n    void                   sendData();\n    void                   queueFrame();\n    void                   sendFrame(bool removeSource = true);\n\n    bool                   m_current = false;\n    WP<CWLSurfaceResource> m_lastSurf;\n\n    WP<CTabletTool>        m_tool;\n    WP<CTabletSeat>        m_seat;\n    wl_event_source*       m_frameSource = nullptr;\n\n    bool                   m_inert = false; // removed was sent\n\n  private:\n    SP<CZwpTabletToolV2> m_resource;\n\n    friend class CTabletSeat;\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletSeat {\n  public:\n    CTabletSeat(SP<CZwpTabletSeatV2> resource_);\n\n    bool                                   good();\n    void                                   sendData();\n\n    std::vector<WP<CTabletToolV2Resource>> m_tools;\n    std::vector<WP<CTabletPadV2Resource>>  m_pads;\n    std::vector<WP<CTabletV2Resource>>     m_tablets;\n\n    void                                   sendTool(SP<CTabletTool> tool);\n    void                                   sendPad(SP<CTabletPad> pad);\n    void                                   sendTablet(SP<CTablet> tablet);\n\n  private:\n    SP<CZwpTabletSeatV2> m_resource;\n    WP<CTabletSeat>      m_self;\n\n    friend class CTabletV2Protocol;\n};\n\nclass CTabletV2Protocol : public IWaylandProtocol {\n  public:\n    CTabletV2Protocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         registerDevice(SP<CTablet> tablet);\n    void         registerDevice(SP<CTabletTool> tool);\n    void         registerDevice(SP<CTabletPad> pad);\n\n    void         unregisterDevice(SP<CTablet> tablet);\n    void         unregisterDevice(SP<CTabletTool> tool);\n    void         unregisterDevice(SP<CTabletPad> pad);\n\n    void         recheckRegisteredDevices();\n\n    // Tablet tool events\n    void pressure(SP<CTabletTool> tool, double value);\n    void distance(SP<CTabletTool> tool, double value);\n    void rotation(SP<CTabletTool> tool, double value);\n    void slider(SP<CTabletTool> tool, double value);\n    void wheel(SP<CTabletTool> tool, double value);\n    void tilt(SP<CTabletTool> tool, const Vector2D& value);\n    void up(SP<CTabletTool> tool);\n    void down(SP<CTabletTool> tool);\n    void proximityIn(SP<CTabletTool> tool, SP<CTablet> tablet, SP<CWLSurfaceResource> surf);\n    void proximityOut(SP<CTabletTool> tool);\n    void buttonTool(SP<CTabletTool> tool, uint32_t button, uint32_t state);\n    void motion(SP<CTabletTool> tool, const Vector2D& value);\n\n    // Tablet pad events\n    void mode(SP<CTabletPad> pad, uint32_t group, uint32_t mode, uint32_t timeMs);\n    void buttonPad(SP<CTabletPad> pad, uint32_t button, uint32_t timeMs, uint32_t state);\n    void strip(SP<CTabletPad> pad, uint32_t strip, double position, bool finger, uint32_t timeMs);\n    void ring(SP<CTabletPad> pad, uint32_t ring, double position, bool finger, uint32_t timeMs);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CTabletSeat* resource);\n    void destroyResource(CTabletToolV2Resource* resource);\n    void destroyResource(CTabletV2Resource* resource);\n    void destroyResource(CTabletPadV2Resource* resource);\n    void destroyResource(CTabletPadGroupV2Resource* resource);\n    void destroyResource(CTabletPadRingV2Resource* resource);\n    void destroyResource(CTabletPadStripV2Resource* resource);\n    void onGetSeat(CZwpTabletManagerV2* pMgr, uint32_t id, wl_resource* seat);\n\n    //\n    std::vector<UP<CZwpTabletManagerV2>>       m_managers;\n    std::vector<SP<CTabletSeat>>               m_seats;\n    std::vector<SP<CTabletToolV2Resource>>     m_tools;\n    std::vector<SP<CTabletV2Resource>>         m_tablets;\n    std::vector<SP<CTabletPadV2Resource>>      m_pads;\n    std::vector<SP<CTabletPadGroupV2Resource>> m_groups;\n    std::vector<SP<CTabletPadRingV2Resource>>  m_rings;\n    std::vector<SP<CTabletPadStripV2Resource>> m_strips;\n\n    // registered\n    std::vector<WP<CTablet>>     m_tabletDevices;\n    std::vector<WP<CTabletTool>> m_toolDevices;\n    std::vector<WP<CTabletPad>>  m_padDevices;\n\n    // FIXME: rings and strips are broken, I don't understand how this shit works.\n    // It's 2am.\n    SP<CTabletPadRingV2Resource>  ringForID(SP<CTabletPad> pad, uint32_t id);\n    SP<CTabletPadStripV2Resource> stripForID(SP<CTabletPad> pad, uint32_t id);\n\n    friend class CTabletSeat;\n    friend class CTabletToolV2Resource;\n    friend class CTabletV2Resource;\n    friend class CTabletPadV2Resource;\n    friend class CTabletPadGroupV2Resource;\n    friend class CTabletPadRingV2Resource;\n    friend class CTabletPadStripV2Resource;\n};\n\nnamespace PROTO {\n    inline UP<CTabletV2Protocol> tablet;\n};\n"
  },
  {
    "path": "src/protocols/TearingControl.cpp",
    "content": "#include \"TearingControl.hpp\"\n#include \"../managers/ProtocolManager.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"../Compositor.hpp\"\n#include \"core/Compositor.hpp\"\n\nCTearingControlProtocol::CTearingControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P = Event::bus()->m_events.window.destroy.listen([this](PHLWINDOW window) { onWindowDestroy(window); });\n}\n\nvoid CTearingControlProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CWpTearingControlManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CWpTearingControlManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CWpTearingControlManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetTearingControl([this](CWpTearingControlManagerV1* pMgr, uint32_t id, wl_resource* surface) {\n        this->onGetController(pMgr->client(), pMgr, id, CWLSurfaceResource::fromResource(surface));\n    });\n}\n\nvoid CTearingControlProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CTearingControlProtocol::onGetController(wl_client* client, CWpTearingControlManagerV1* pMgr, uint32_t id, SP<CWLSurfaceResource> surf) {\n    const auto CONTROLLER = m_tearingControllers.emplace_back(makeUnique<CTearingControl>(makeShared<CWpTearingControlV1>(client, pMgr->version(), id), surf)).get();\n\n    if UNLIKELY (!CONTROLLER->good()) {\n        pMgr->noMemory();\n        m_tearingControllers.pop_back();\n        return;\n    }\n}\n\nvoid CTearingControlProtocol::onControllerDestroy(CTearingControl* control) {\n    std::erase_if(m_tearingControllers, [control](const auto& other) { return other.get() == control; });\n}\n\nvoid CTearingControlProtocol::onWindowDestroy(PHLWINDOW pWindow) {\n    for (auto const& c : m_tearingControllers) {\n        if (c->m_window.lock() == pWindow)\n            c->m_window.reset();\n    }\n}\n\n//\n\nCTearingControl::CTearingControl(SP<CWpTearingControlV1> resource_, SP<CWLSurfaceResource> surf_) : m_resource(resource_) {\n    m_resource->setData(this);\n    m_resource->setOnDestroy([this](CWpTearingControlV1* res) { PROTO::tearing->onControllerDestroy(this); });\n    m_resource->setDestroy([this](CWpTearingControlV1* res) { PROTO::tearing->onControllerDestroy(this); });\n    m_resource->setSetPresentationHint([this](CWpTearingControlV1* res, wpTearingControlV1PresentationHint hint) { this->onHint(hint); });\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->wlSurface()->resource() == surf_) {\n            m_window = w;\n            break;\n        }\n    }\n}\n\nvoid CTearingControl::onHint(wpTearingControlV1PresentationHint hint_) {\n    m_hint = hint_;\n    updateWindow();\n}\n\nvoid CTearingControl::updateWindow() {\n    if UNLIKELY (m_window.expired())\n        return;\n\n    m_window->m_tearingHint = m_hint == WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC;\n}\n\nbool CTearingControl::good() {\n    return m_resource->resource();\n}\n"
  },
  {
    "path": "src/protocols/TearingControl.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"tearing-control-v1.hpp\"\n\nclass CTearingControlProtocol;\nclass CWLSurfaceResource;\n\nclass CTearingControl {\n  public:\n    CTearingControl(SP<CWpTearingControlV1> resource_, SP<CWLSurfaceResource> surf_);\n\n    void onHint(wpTearingControlV1PresentationHint hint_);\n\n    bool good();\n\n    bool operator==(const wl_resource* other) const {\n        return other == m_resource->resource();\n    }\n\n    bool operator==(const CTearingControl* other) const {\n        return other->m_resource == m_resource;\n    }\n\n  private:\n    void                               updateWindow();\n\n    SP<CWpTearingControlV1>            m_resource;\n    PHLWINDOWREF                       m_window;\n    wpTearingControlV1PresentationHint m_hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC;\n\n    friend class CTearingControlProtocol;\n};\n\nclass CTearingControlProtocol : public IWaylandProtocol {\n  public:\n    CTearingControlProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void onControllerDestroy(CTearingControl* control);\n    void onGetController(wl_client* client, CWpTearingControlManagerV1* pMgr, uint32_t id, SP<CWLSurfaceResource> surf);\n    void onWindowDestroy(PHLWINDOW pWindow);\n\n    //\n    std::vector<UP<CWpTearingControlManagerV1>> m_managers;\n    std::vector<UP<CTearingControl>>            m_tearingControllers;\n\n    friend class CTearingControl;\n};\n\nnamespace PROTO {\n    inline UP<CTearingControlProtocol> tearing;\n};"
  },
  {
    "path": "src/protocols/TextInputV1.cpp",
    "content": "#include \"TextInputV1.hpp\"\n\n#include \"core/Compositor.hpp\"\n\nCTextInputV1::~CTextInputV1() {\n    m_events.destroy.emit();\n}\n\nCTextInputV1::CTextInputV1(SP<CZwpTextInputV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CZwpTextInputV1* pMgr) { PROTO::textInputV1->destroyResource(this); });\n\n    m_resource->setActivate([this](CZwpTextInputV1* pMgr, wl_resource* seat, wl_resource* surface) {\n        if UNLIKELY (!surface) {\n            LOGM(Log::WARN, \"Text-input-v1 PTI{:x}: No surface to activate text input on!\", (uintptr_t)this);\n            return;\n        }\n\n        m_active = true;\n        m_events.enable.emit(CWLSurfaceResource::fromResource(surface));\n    });\n\n    m_resource->setDeactivate([this](CZwpTextInputV1* pMgr, wl_resource* seat) {\n        m_active = false;\n        m_events.disable.emit();\n    });\n\n    m_resource->setReset([this](CZwpTextInputV1* pMgr) {\n        m_pendingSurrounding.isPending = false;\n        m_pendingContentType.isPending = false;\n        m_events.reset.emit();\n    });\n\n    m_resource->setSetSurroundingText(\n        [this](CZwpTextInputV1* pMgr, const char* text, uint32_t cursor, uint32_t anchor) { m_pendingSurrounding = {true, std::string(text), cursor, anchor}; });\n\n    m_resource->setSetContentType([this](CZwpTextInputV1* pMgr, uint32_t hint, uint32_t purpose) {\n        m_pendingContentType = {true, hint == sc<uint32_t>(ZWP_TEXT_INPUT_V1_CONTENT_HINT_DEFAULT) ? sc<uint32_t>(ZWP_TEXT_INPUT_V1_CONTENT_HINT_NONE) : hint,\n                                purpose > sc<uint32_t>(ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PASSWORD) ? hint + 1 : hint};\n    });\n\n    m_resource->setSetCursorRectangle([this](CZwpTextInputV1* pMgr, int32_t x, int32_t y, int32_t width, int32_t height) { m_cursorRectangle = CBox{x, y, width, height}; });\n\n    m_resource->setCommitState([this](CZwpTextInputV1* pMgr, uint32_t serial_) {\n        m_serial = serial_;\n        m_events.onCommit.emit();\n    });\n\n    // nothing\n    m_resource->setShowInputPanel([](CZwpTextInputV1* pMgr) {});\n    m_resource->setHideInputPanel([](CZwpTextInputV1* pMgr) {});\n    m_resource->setSetPreferredLanguage([](CZwpTextInputV1* pMgr, const char* language) {});\n    m_resource->setInvokeAction([](CZwpTextInputV1* pMgr, uint32_t button, uint32_t index) {});\n}\n\nbool CTextInputV1::good() {\n    return m_resource->resource();\n}\n\nwl_client* CTextInputV1::client() {\n    return m_resource->client();\n}\n\nvoid CTextInputV1::enter(SP<CWLSurfaceResource> surface) {\n    m_resource->sendEnter(surface->getResource()->resource());\n    m_active = true;\n}\n\nvoid CTextInputV1::leave() {\n    m_resource->sendLeave();\n    m_active = false;\n}\n\nvoid CTextInputV1::preeditCursor(int32_t index) {\n    m_resource->sendPreeditCursor(index);\n}\n\nvoid CTextInputV1::preeditStyling(uint32_t index, uint32_t length, zwpTextInputV1PreeditStyle style) {\n    m_resource->sendPreeditStyling(index, length, style);\n}\n\nvoid CTextInputV1::preeditString(uint32_t serial, const char* text, const char* commit) {\n    m_resource->sendPreeditString(serial, text, commit);\n}\n\nvoid CTextInputV1::commitString(uint32_t serial, const char* text) {\n    m_resource->sendCommitString(serial, text);\n}\n\nvoid CTextInputV1::deleteSurroundingText(int32_t index, uint32_t length) {\n    m_resource->sendDeleteSurroundingText(index, length);\n}\n\nCTextInputV1Protocol::CTextInputV1Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CTextInputV1Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CZwpTextInputManagerV1>(client, ver, id));\n\n    RESOURCE->setOnDestroy([](CZwpTextInputManagerV1* pMgr) { PROTO::textInputV1->destroyResource(pMgr); });\n    RESOURCE->setCreateTextInput([this](CZwpTextInputManagerV1* pMgr, uint32_t id) {\n        const auto PTI = m_clients.emplace_back(makeShared<CTextInputV1>(makeShared<CZwpTextInputV1>(pMgr->client(), pMgr->version(), id)));\n        LOGM(Log::DEBUG, \"New TI V1 at {:x}\", (uintptr_t)PTI.get());\n\n        if UNLIKELY (!PTI->good()) {\n            LOGM(Log::ERR, \"Could not alloc wl_resource for TIV1\");\n            pMgr->noMemory();\n            PROTO::textInputV1->destroyResource(PTI.get());\n            return;\n        }\n\n        m_events.newTextInput.emit(WP<CTextInputV1>(PTI));\n    });\n}\n\nvoid CTextInputV1Protocol::destroyResource(CTextInputV1* client) {\n    std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; });\n}\n\nvoid CTextInputV1Protocol::destroyResource(CZwpTextInputManagerV1* client) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == client; });\n}\n"
  },
  {
    "path": "src/protocols/TextInputV1.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"text-input-unstable-v1.hpp\"\n#include \"WaylandProtocol.hpp\"\n\n#include <vector>\n\nclass CTextInput;\n\nclass CTextInputV1 {\n  public:\n    CTextInputV1(SP<CZwpTextInputV1> resource);\n    ~CTextInputV1();\n\n    void       enter(SP<CWLSurfaceResource> surface);\n    void       leave();\n\n    void       preeditCursor(int32_t index);\n    void       preeditStyling(uint32_t index, uint32_t length, zwpTextInputV1PreeditStyle style);\n    void       preeditString(uint32_t serial, const char* text, const char* commit);\n    void       commitString(uint32_t serial, const char* text);\n    void       deleteSurroundingText(int32_t index, uint32_t length);\n\n    bool       good();\n    wl_client* client();\n\n  private:\n    SP<CZwpTextInputV1> m_resource;\n\n    uint32_t            m_serial = 0;\n    bool                m_active = false;\n\n    struct {\n        CSignalT<>                       onCommit;\n        CSignalT<SP<CWLSurfaceResource>> enable;\n        CSignalT<>                       disable;\n        CSignalT<>                       reset;\n        CSignalT<>                       destroy;\n    } m_events;\n\n    struct SPendingSurr {\n        bool        isPending = false;\n        std::string text      = \"\";\n        uint32_t    cursor    = 0;\n        uint32_t    anchor    = 0;\n    } m_pendingSurrounding;\n\n    struct SPendingCT {\n        bool     isPending = false;\n        uint32_t hint      = 0;\n        uint32_t purpose   = 0;\n    } m_pendingContentType;\n\n    CBox m_cursorRectangle = {0, 0, 0, 0};\n\n    friend class CTextInput;\n    friend class CTextInputV1Protocol;\n};\n\nclass CTextInputV1Protocol : public IWaylandProtocol {\n  public:\n    CTextInputV1Protocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t version, uint32_t id);\n    void         destroyResource(CTextInputV1* resource);\n    void         destroyResource(CZwpTextInputManagerV1* client);\n\n    struct {\n        CSignalT<WP<CTextInputV1>> newTextInput;\n    } m_events;\n\n  private:\n    std::vector<SP<CZwpTextInputManagerV1>> m_managers;\n    std::vector<SP<CTextInputV1>>           m_clients;\n\n    friend class CTextInputV1;\n};\n\nnamespace PROTO {\n    inline UP<CTextInputV1Protocol> textInputV1;\n};\n"
  },
  {
    "path": "src/protocols/TextInputV3.cpp",
    "content": "#include \"TextInputV3.hpp\"\n#include <algorithm>\n#include \"core/Compositor.hpp\"\n\nvoid CTextInputV3::SState::reset() {\n    cause               = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD;\n    surrounding.updated = false;\n    contentType.updated = false;\n    box.updated         = false;\n}\n\nCTextInputV3::CTextInputV3(SP<CZwpTextInputV3> resource_) : m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    LOGM(Log::DEBUG, \"New tiv3 at {:016x}\", (uintptr_t)this);\n\n    m_resource->setDestroy([this](CZwpTextInputV3* r) { PROTO::textInputV3->destroyTextInput(this); });\n    m_resource->setOnDestroy([this](CZwpTextInputV3* r) { PROTO::textInputV3->destroyTextInput(this); });\n\n    m_resource->setCommit([this](CZwpTextInputV3* r) {\n        bool wasEnabled = m_current.enabled.value;\n\n        m_current = m_pending;\n        m_serial++;\n\n        if (wasEnabled && !m_current.enabled.value)\n            m_events.disable.emit();\n        else if (!wasEnabled && m_current.enabled.value)\n            m_events.enable.emit();\n        else if (m_current.enabled.value && m_current.enabled.isEnablePending && m_current.enabled.isDisablePending)\n            m_events.reset.emit();\n        else\n            m_events.onCommit.emit();\n\n        m_pending.enabled.isEnablePending  = false;\n        m_pending.enabled.isDisablePending = false;\n    });\n\n    m_resource->setSetSurroundingText([this](CZwpTextInputV3* r, const char* text, int32_t cursor, int32_t anchor) {\n        m_pending.surrounding.updated = true;\n        m_pending.surrounding.anchor  = anchor;\n        m_pending.surrounding.cursor  = cursor;\n        m_pending.surrounding.text    = text;\n    });\n\n    m_resource->setSetTextChangeCause([this](CZwpTextInputV3* r, zwpTextInputV3ChangeCause cause) { m_pending.cause = cause; });\n\n    m_resource->setSetContentType([this](CZwpTextInputV3* r, zwpTextInputV3ContentHint hint, zwpTextInputV3ContentPurpose purpose) {\n        m_pending.contentType.updated = true;\n        m_pending.contentType.hint    = hint;\n        m_pending.contentType.purpose = purpose;\n    });\n\n    m_resource->setSetCursorRectangle([this](CZwpTextInputV3* r, int32_t x, int32_t y, int32_t w, int32_t h) {\n        m_pending.box.updated   = true;\n        m_pending.box.cursorBox = {x, y, w, h};\n    });\n\n    m_resource->setEnable([this](CZwpTextInputV3* r) {\n        m_pending.reset();\n        m_pending.enabled.value           = true;\n        m_pending.enabled.isEnablePending = true;\n    });\n\n    m_resource->setDisable([this](CZwpTextInputV3* r) {\n        m_pending.enabled.value            = false;\n        m_pending.enabled.isDisablePending = true;\n    });\n}\n\nCTextInputV3::~CTextInputV3() {\n    m_events.destroy.emit();\n}\n\nvoid CTextInputV3::enter(SP<CWLSurfaceResource> surf) {\n    m_resource->sendEnter(surf->getResource()->resource());\n}\n\nvoid CTextInputV3::leave(SP<CWLSurfaceResource> surf) {\n    m_resource->sendLeave(surf->getResource()->resource());\n}\n\nvoid CTextInputV3::preeditString(const std::string& text, int32_t cursorBegin, int32_t cursorEnd) {\n    m_resource->sendPreeditString(text.c_str(), cursorBegin, cursorEnd);\n}\n\nvoid CTextInputV3::commitString(const std::string& text) {\n    m_resource->sendCommitString(text.c_str());\n}\n\nvoid CTextInputV3::deleteSurroundingText(uint32_t beforeLength, uint32_t afterLength) {\n    m_resource->sendDeleteSurroundingText(beforeLength, afterLength);\n}\n\nvoid CTextInputV3::sendDone() {\n    m_resource->sendDone(m_serial);\n}\n\nbool CTextInputV3::good() {\n    return m_resource->resource();\n}\n\nwl_client* CTextInputV3::client() {\n    return wl_resource_get_client(m_resource->resource());\n}\n\nCTextInputV3Protocol::CTextInputV3Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CTextInputV3Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpTextInputManagerV3>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpTextInputManagerV3* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZwpTextInputManagerV3* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetTextInput([this](CZwpTextInputManagerV3* pMgr, uint32_t id, wl_resource* seat) { this->onGetTextInput(pMgr, id, seat); });\n}\n\nvoid CTextInputV3Protocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CTextInputV3Protocol::destroyTextInput(CTextInputV3* input) {\n    std::erase_if(m_textInputs, [&](const auto& other) { return other.get() == input; });\n}\n\nvoid CTextInputV3Protocol::onGetTextInput(CZwpTextInputManagerV3* pMgr, uint32_t id, wl_resource* seat) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_textInputs.emplace_back(makeShared<CTextInputV3>(makeShared<CZwpTextInputV3>(CLIENT, pMgr->version(), id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_textInputs.pop_back();\n        LOGM(Log::ERR, \"Failed to create a tiv3 resource\");\n        return;\n    }\n\n    m_events.newTextInput.emit(WP<CTextInputV3>(RESOURCE));\n}"
  },
  {
    "path": "src/protocols/TextInputV3.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include <string>\n#include \"WaylandProtocol.hpp\"\n#include \"text-input-unstable-v3.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/math/Math.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CTextInputV3 {\n  public:\n    CTextInputV3(SP<CZwpTextInputV3> resource_);\n    ~CTextInputV3();\n\n    void       enter(SP<CWLSurfaceResource> surf);\n    void       leave(SP<CWLSurfaceResource> surf);\n    void       preeditString(const std::string& text, int32_t cursorBegin, int32_t cursorEnd);\n    void       commitString(const std::string& text);\n    void       deleteSurroundingText(uint32_t beforeLength, uint32_t afterLength);\n    void       sendDone();\n\n    bool       good();\n\n    wl_client* client();\n\n    struct {\n        CSignalT<> onCommit;\n        CSignalT<> enable;\n        CSignalT<> disable;\n        CSignalT<> reset;\n        CSignalT<> destroy;\n    } m_events;\n\n    struct SState {\n        struct {\n            bool        updated = false;\n            std::string text    = \"\";\n            uint32_t    cursor  = 0;\n            uint32_t    anchor  = 0;\n        } surrounding;\n\n        struct {\n            bool                         updated = false;\n            zwpTextInputV3ContentHint    hint    = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;\n            zwpTextInputV3ContentPurpose purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;\n        } contentType;\n\n        struct {\n            bool updated = false;\n            CBox cursorBox;\n        } box;\n\n        struct {\n            bool isEnablePending  = false;\n            bool isDisablePending = false;\n            bool value            = false;\n        } enabled;\n\n        zwpTextInputV3ChangeCause cause = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD;\n\n        void                      reset();\n    };\n\n    SState m_pending;\n    SState m_current;\n\n  private:\n    SP<CZwpTextInputV3> m_resource;\n\n    int                 m_serial = 0;\n};\n\nclass CTextInputV3Protocol : public IWaylandProtocol {\n  public:\n    CTextInputV3Protocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<WP<CTextInputV3>> newTextInput;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyTextInput(CTextInputV3* input);\n    void onGetTextInput(CZwpTextInputManagerV3* pMgr, uint32_t id, wl_resource* seat);\n\n    //\n    std::vector<UP<CZwpTextInputManagerV3>> m_managers;\n    std::vector<SP<CTextInputV3>>           m_textInputs;\n\n    friend class CTextInputV3;\n};\n\nnamespace PROTO {\n    inline UP<CTextInputV3Protocol> textInputV3;\n};\n"
  },
  {
    "path": "src/protocols/ToplevelExport.cpp",
    "content": "#include \"ToplevelExport.hpp\"\n#include \"../Compositor.hpp\"\n#include \"ForeignToplevelWlr.hpp\"\n#include \"../managers/screenshare/ScreenshareManager.hpp\"\n#include \"../helpers/Format.hpp\"\n#include \"../render/Renderer.hpp\"\n\n#include <hyprutils/math/Vector2D.hpp>\n\nusing namespace Screenshare;\n\nCToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); });\n    m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); });\n    m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) {\n        captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle));\n    });\n    m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) {\n        captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle));\n    });\n\n    m_savedClient = m_resource->client();\n}\n\nvoid CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) {\n    if UNLIKELY (!handle) {\n        LOGM(Log::ERR, \"Couldn't capture (window doesn't exist)\");\n        return;\n    }\n\n    auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle);\n\n    // create a frame\n    const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back(\n        makeShared<CToplevelExportFrame>(makeShared<CHyprlandToplevelExportFrameV1>(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_));\n\n    if UNLIKELY (!FRAME->good()) {\n        LOGM(Log::ERR, \"Couldn't alloc frame for sharing! (no memory)\");\n        m_resource->noMemory();\n        PROTO::toplevelExport->destroyResource(FRAME.get());\n        return;\n    }\n\n    FRAME->m_client = m_self;\n    FRAME->m_self   = FRAME;\n}\n\nbool CToplevelExportClient::good() {\n    return m_resource && m_resource->resource();\n}\n\nCToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource_, WP<CScreenshareSession> session, bool overlayCursor) :\n    m_resource(resource_), m_session(session) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); });\n    m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); });\n    m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); });\n\n    m_frame = m_session->nextFrame(overlayCursor);\n\n    auto formats = m_session->allowedFormats();\n    if (formats.empty()) {\n        LOGM(Log::ERR, \"No format supported by renderer in toplevel export protocol\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    DRMFormat  format  = formats.at(0);\n    auto       bufSize = m_frame->bufferSize();\n\n    const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format);\n    const auto stride   = NFormatUtils::minStride(PSHMINFO, bufSize.x);\n    m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride);\n\n    if LIKELY (format != DRM_FORMAT_INVALID)\n        m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y);\n\n    m_resource->sendBufferDone();\n}\n\nbool CToplevelExportFrame::good() {\n    return m_resource && m_resource->resource();\n}\n\nvoid CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) {\n    if UNLIKELY (!good()) {\n        LOGM(Log::ERR, \"No frame in shareFrame??\");\n        return;\n    }\n\n    if UNLIKELY (m_session.expired() || !m_session->monitor()) {\n        LOGM(Log::ERR, \"Session stopped for frame {:x}\", (uintptr_t)this);\n        m_resource->sendFailed();\n        return;\n    }\n\n    if UNLIKELY (m_buffer) {\n        LOGM(Log::ERR, \"Buffer used in {:x}\", (uintptr_t)this);\n        m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, \"frame already used\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    const auto PBUFFERRES = CWLBufferResource::fromResource(buffer);\n    if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) {\n        LOGM(Log::ERR, \"Invalid buffer in {:x}\", (uintptr_t)this);\n        m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, \"invalid buffer\");\n        m_resource->sendFailed();\n        return;\n    }\n\n    const auto& PBUFFER = PBUFFERRES->m_buffer.lock();\n\n    if (ignoreDamage)\n        g_pHyprRenderer->damageMonitor(m_session->monitor());\n\n    auto error = m_frame->share(PBUFFER, {}, [this, ignoreDamage, self = m_self](eScreenshareResult result) {\n        if (self.expired() || !good())\n            return;\n        switch (result) {\n            case RESULT_COPIED: {\n                m_resource->sendFlags(sc<hyprlandToplevelExportFrameV1Flags>(0));\n                if (!ignoreDamage)\n                    m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); });\n\n                const auto [sec, nsec] = Time::secNsec(m_timestamp);\n                uint32_t tvSecHi       = (sizeof(sec) > 4) ? sec >> 32 : 0;\n                uint32_t tvSecLo       = sec & 0xFFFFFFFF;\n                m_resource->sendReady(tvSecHi, tvSecLo, nsec);\n                break;\n            }\n            case RESULT_NOT_COPIED:\n                LOGM(Log::ERR, \"Frame share failed in {:x}\", (uintptr_t)this);\n                m_resource->sendFailed();\n                break;\n            case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break;\n        }\n    });\n\n    switch (error) {\n        case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break;\n        case ERROR_NO_BUFFER: m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, \"invalid buffer\"); break;\n        case ERROR_BUFFER_SIZE:\n        case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break;\n        case ERROR_UNKNOWN:\n        case ERROR_STOPPED: m_resource->sendFailed(); break;\n    }\n}\n\nCToplevelExportProtocol::CToplevelExportProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto CLIENT = m_clients.emplace_back(makeShared<CToplevelExportClient>(makeShared<CHyprlandToplevelExportManagerV1>(client, ver, id)));\n\n    if (!CLIENT->good()) {\n        LOGM(Log::DEBUG, \"Failed to bind client! (out of memory)\");\n        wl_client_post_no_memory(client);\n        m_clients.pop_back();\n        return;\n    }\n\n    CLIENT->m_self = CLIENT;\n\n    LOGM(Log::DEBUG, \"Bound client successfully!\");\n}\n\nvoid CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) {\n    std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; });\n    std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; });\n}\n\nvoid CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) {\n    std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; });\n}\n"
  },
  {
    "path": "src/protocols/ToplevelExport.hpp",
    "content": "#pragma once\n\n#include \"WaylandProtocol.hpp\"\n#include \"hyprland-toplevel-export-v1.hpp\"\n\n#include \"../helpers/time/Time.hpp\"\n#include \"./types/Buffer.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n\n#include <vector>\n\nclass CMonitor;\nnamespace Screenshare {\n    class CScreenshareSession;\n    class CScreenshareFrame;\n};\n\nclass CToplevelExportClient {\n  public:\n    CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CHyprlandToplevelExportManagerV1> m_resource;\n    WP<CToplevelExportClient>            m_self;\n\n    wl_client*                           m_savedClient = nullptr;\n\n    void                                 captureToplevel(uint32_t frame, int32_t overlayCursor, PHLWINDOW handle);\n\n    friend class CToplevelExportProtocol;\n};\n\nclass CToplevelExportFrame {\n  public:\n    CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource, WP<Screenshare::CScreenshareSession> session, bool overlayCursor);\n\n    bool good();\n\n  private:\n    SP<CHyprlandToplevelExportFrameV1>   m_resource;\n    WP<CToplevelExportFrame>             m_self;\n    WP<CToplevelExportClient>            m_client;\n\n    WP<Screenshare::CScreenshareSession> m_session;\n    UP<Screenshare::CScreenshareFrame>   m_frame;\n\n    CHLBufferReference                   m_buffer;\n    Time::steady_tp                      m_timestamp;\n\n    //\n    void shareFrame(wl_resource* buffer, bool ignoreDamage);\n\n    friend class CToplevelExportProtocol;\n    friend class CToplevelExportClient;\n};\n\nclass CToplevelExportProtocol : IWaylandProtocol {\n  public:\n    CToplevelExportProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n    void destroyResource(CToplevelExportClient* client);\n    void destroyResource(CToplevelExportFrame* frame);\n\n    void onOutputCommit(PHLMONITOR pMonitor);\n\n  private:\n    std::vector<SP<CToplevelExportClient>> m_clients;\n    std::vector<SP<CToplevelExportFrame>>  m_frames;\n\n    void                                   onWindowUnmap(PHLWINDOW pWindow);\n\n    friend class CToplevelExportClient;\n    friend class CToplevelExportFrame;\n};\n\nnamespace PROTO {\n    inline UP<CToplevelExportProtocol> toplevelExport;\n};\n"
  },
  {
    "path": "src/protocols/ToplevelMapping.cpp",
    "content": "#include \"ToplevelMapping.hpp\"\n#include \"hyprland-toplevel-mapping-v1.hpp\"\n#include \"ForeignToplevelWlr.hpp\"\n#include \"ForeignToplevel.hpp\"\n\nCToplevelWindowMappingHandle::CToplevelWindowMappingHandle(SP<CHyprlandToplevelWindowMappingHandleV1> resource_) : m_resource(resource_) {}\n\nCToplevelMappingManager::CToplevelMappingManager(SP<CHyprlandToplevelMappingManagerV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setOnDestroy([this](CHyprlandToplevelMappingManagerV1* h) { PROTO::toplevelMapping->onManagerResourceDestroy(this); });\n    m_resource->setDestroy([this](CHyprlandToplevelMappingManagerV1* h) { PROTO::toplevelMapping->onManagerResourceDestroy(this); });\n\n    m_resource->setGetWindowForToplevel([this](CHyprlandToplevelMappingManagerV1* mgr, uint32_t handle, wl_resource* toplevel) {\n        const auto NEWHANDLE = PROTO::toplevelMapping->m_handles.emplace_back(\n            makeShared<CToplevelWindowMappingHandle>(makeShared<CHyprlandToplevelWindowMappingHandleV1>(m_resource->client(), m_resource->version(), handle)));\n\n        if UNLIKELY (!NEWHANDLE->m_resource->resource()) {\n            LOGM(Log::ERR, \"Couldn't alloc mapping handle! (no memory)\");\n            m_resource->noMemory();\n            return;\n        }\n\n        NEWHANDLE->m_resource->setOnDestroy([](CHyprlandToplevelWindowMappingHandleV1* h) { PROTO::toplevelMapping->destroyHandle(h); });\n        NEWHANDLE->m_resource->setDestroy([](CHyprlandToplevelWindowMappingHandleV1* h) { PROTO::toplevelMapping->destroyHandle(h); });\n\n        const auto WINDOW = PROTO::foreignToplevel->windowFromHandleResource(toplevel);\n        if (!WINDOW)\n            NEWHANDLE->m_resource->sendFailed();\n        else\n            NEWHANDLE->m_resource->sendWindowAddress(rc<uint64_t>(WINDOW.get()) >> 32 & 0xFFFFFFFF, rc<uint64_t>(WINDOW.get()) & 0xFFFFFFFF);\n    });\n    m_resource->setGetWindowForToplevelWlr([this](CHyprlandToplevelMappingManagerV1* mgr, uint32_t handle, wl_resource* toplevel) {\n        const auto NEWHANDLE = PROTO::toplevelMapping->m_handles.emplace_back(\n            makeShared<CToplevelWindowMappingHandle>(makeShared<CHyprlandToplevelWindowMappingHandleV1>(m_resource->client(), m_resource->version(), handle)));\n\n        if UNLIKELY (!NEWHANDLE->m_resource->resource()) {\n            LOGM(Log::ERR, \"Couldn't alloc mapping handle! (no memory)\");\n            m_resource->noMemory();\n            return;\n        }\n\n        NEWHANDLE->m_resource->setOnDestroy([](CHyprlandToplevelWindowMappingHandleV1* h) { PROTO::toplevelMapping->destroyHandle(h); });\n        NEWHANDLE->m_resource->setDestroy([](CHyprlandToplevelWindowMappingHandleV1* h) { PROTO::toplevelMapping->destroyHandle(h); });\n\n        const auto WINDOW = PROTO::foreignToplevelWlr->windowFromHandleResource(toplevel);\n        if (!WINDOW)\n            NEWHANDLE->m_resource->sendFailed();\n        else\n            NEWHANDLE->m_resource->sendWindowAddress(rc<uint64_t>(WINDOW.get()) >> 32 & 0xFFFFFFFF, rc<uint64_t>(WINDOW.get()) & 0xFFFFFFFF);\n    });\n}\n\nbool CToplevelMappingManager::good() const {\n    return m_resource->resource();\n}\n\nCToplevelMappingProtocol::CToplevelMappingProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {}\n\nvoid CToplevelMappingProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CToplevelMappingManager>(makeShared<CHyprlandToplevelMappingManagerV1>(client, ver, id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        LOGM(Log::ERR, \"Couldn't create a toplevel mapping manager\");\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CToplevelMappingProtocol::onManagerResourceDestroy(CToplevelMappingManager* mgr) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == mgr; });\n}\n\nvoid CToplevelMappingProtocol::destroyHandle(CHyprlandToplevelWindowMappingHandleV1* handle) {\n    std::erase_if(m_handles, [&](const auto& other) { return other->m_resource.get() == handle; });\n}"
  },
  {
    "path": "src/protocols/ToplevelMapping.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"hyprland-toplevel-mapping-v1.hpp\"\n\nclass CToplevelWindowMappingHandle {\n  public:\n    CToplevelWindowMappingHandle(SP<CHyprlandToplevelWindowMappingHandleV1> resource_);\n\n  private:\n    SP<CHyprlandToplevelWindowMappingHandleV1> m_resource;\n\n    friend class CToplevelMappingManager;\n    friend class CToplevelMappingProtocol;\n};\n\nclass CToplevelMappingManager {\n  public:\n    CToplevelMappingManager(SP<CHyprlandToplevelMappingManagerV1> resource_);\n\n    bool good() const;\n\n  private:\n    SP<CHyprlandToplevelMappingManagerV1> m_resource;\n};\n\nclass CToplevelMappingProtocol : IWaylandProtocol {\n  public:\n    CToplevelMappingProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void                                          onManagerResourceDestroy(CToplevelMappingManager* mgr);\n    void                                          destroyHandle(CHyprlandToplevelWindowMappingHandleV1* handle);\n\n    std::vector<UP<CToplevelMappingManager>>      m_managers;\n    std::vector<SP<CToplevelWindowMappingHandle>> m_handles;\n\n    friend class CToplevelMappingManager;\n};\n\nnamespace PROTO {\n    inline UP<CToplevelMappingProtocol> toplevelMapping;\n};"
  },
  {
    "path": "src/protocols/Viewporter.cpp",
    "content": "#include \"Viewporter.hpp\"\n#include \"core/Compositor.hpp\"\n#include <algorithm>\n#include <cmath>\n\nCViewportResource::CViewportResource(SP<CWpViewport> resource_, SP<CWLSurfaceResource> surface_) : m_surface(surface_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWpViewport* r) { PROTO::viewport->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpViewport* r) { PROTO::viewport->destroyResource(this); });\n\n    m_resource->setSetDestination([this](CWpViewport* r, int32_t x, int32_t y) {\n        if UNLIKELY (!m_surface) {\n            r->error(WP_VIEWPORT_ERROR_NO_SURFACE, \"Surface is gone\");\n            return;\n        }\n\n        m_surface->m_pending.updated.bits.viewport = true;\n\n        if (x == -1 && y == -1) {\n            m_surface->m_pending.viewport.hasDestination = false;\n            return;\n        }\n\n        if UNLIKELY (x <= 0 || y <= 0) {\n            r->error(WP_VIEWPORT_ERROR_BAD_SIZE, \"Size was <= 0\");\n            return;\n        }\n\n        m_surface->m_pending.viewport.hasDestination = true;\n        m_surface->m_pending.viewport.destination    = {x, y};\n    });\n\n    m_resource->setSetSource([this](CWpViewport* r, wl_fixed_t fx, wl_fixed_t fy, wl_fixed_t fw, wl_fixed_t fh) {\n        if UNLIKELY (!m_surface) {\n            r->error(WP_VIEWPORT_ERROR_NO_SURFACE, \"Surface is gone\");\n            return;\n        }\n\n        m_surface->m_pending.updated.bits.viewport = true;\n\n        double x = wl_fixed_to_double(fx), y = wl_fixed_to_double(fy), w = wl_fixed_to_double(fw), h = wl_fixed_to_double(fh);\n\n        if (x == -1 && y == -1 && w == -1 && h == -1) {\n            m_surface->m_pending.viewport.hasSource = false;\n            return;\n        }\n\n        if UNLIKELY (x < 0 || y < 0) {\n            r->error(WP_VIEWPORT_ERROR_BAD_SIZE, \"Pos was < 0\");\n            return;\n        }\n\n        m_surface->m_pending.viewport.hasSource = true;\n        m_surface->m_pending.viewport.source    = {x, y, w, h};\n    });\n\n    m_listeners.surfacePrecommit = m_surface->m_events.precommit.listen([this] {\n        if (!m_surface || !m_surface->m_pending.buffer)\n            return;\n\n        if (m_surface->m_pending.viewport.hasSource) {\n            auto&       src  = m_surface->m_pending.viewport.source;\n            const auto& size = m_surface->m_pending.bufferSize;\n\n            if (size.x <= 0.0 || size.y <= 0.0)\n                return;\n\n            constexpr wl_fixed_t MAX_TOLERANCE = 1 << 8; // wl_fixed 1.0 = 256\n\n            auto                 clampAxis = [&](double& start, double& length, double limit) -> bool {\n                const double     originalStart = start;\n                const double     originalEnd   = start + length;\n                const double     clampedStart  = std::clamp(start, 0.0, std::max(0.0, limit));\n                const double     clampedEnd    = std::clamp(originalEnd, 0.0, std::max(0.0, limit));\n                const wl_fixed_t startDelta    = std::abs(wl_fixed_from_double(clampedStart) - wl_fixed_from_double(originalStart));\n                const wl_fixed_t endDelta      = std::abs(wl_fixed_from_double(clampedEnd) - wl_fixed_from_double(originalEnd));\n\n                if (startDelta > MAX_TOLERANCE || endDelta > MAX_TOLERANCE)\n                    return false;\n\n                start  = clampedStart;\n                length = clampedEnd - clampedStart;\n                return length > 0.0;\n            };\n\n            if (!clampAxis(src.x, src.w, size.x) || !clampAxis(src.y, src.h, size.y)) {\n                m_resource->error(WP_VIEWPORT_ERROR_BAD_VALUE, \"Box doesn't fit\");\n                m_surface->m_pending.rejected = true;\n                return;\n            }\n        }\n    });\n}\n\nCViewportResource::~CViewportResource() {\n    if (!m_surface)\n        return;\n\n    m_surface->m_pending.viewport.hasDestination = false;\n    m_surface->m_pending.viewport.hasSource      = false;\n}\n\nbool CViewportResource::good() {\n    return m_resource->resource();\n}\n\nCViewporterResource::CViewporterResource(SP<CWpViewporter> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWpViewporter* r) { PROTO::viewport->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWpViewporter* r) { PROTO::viewport->destroyResource(this); });\n\n    m_resource->setGetViewport([](CWpViewporter* r, uint32_t id, wl_resource* surf) {\n        const auto RESOURCE = PROTO::viewport->m_viewports.emplace_back(\n            makeShared<CViewportResource>(makeShared<CWpViewport>(r->client(), r->version(), id), CWLSurfaceResource::fromResource(surf)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::viewport->m_viewports.pop_back();\n            return;\n        }\n    });\n}\n\nbool CViewporterResource::good() {\n    return m_resource->resource();\n}\n\nCViewporterProtocol::CViewporterProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CViewporterProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CViewporterResource>(makeShared<CWpViewporter>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CViewporterProtocol::destroyResource(CViewporterResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CViewporterProtocol::destroyResource(CViewportResource* resource) {\n    std::erase_if(m_viewports, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/Viewporter.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"viewporter.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CViewportResource {\n  public:\n    CViewportResource(SP<CWpViewport> resource_, SP<CWLSurfaceResource> surface_);\n    ~CViewportResource();\n\n    bool                   good();\n    WP<CWLSurfaceResource> m_surface;\n\n  private:\n    SP<CWpViewport> m_resource;\n\n    struct {\n        CHyprSignalListener surfacePrecommit;\n    } m_listeners;\n};\n\nclass CViewporterResource {\n  public:\n    CViewporterResource(SP<CWpViewporter> resource_);\n\n    bool good();\n\n  private:\n    SP<CWpViewporter> m_resource;\n};\n\nclass CViewporterProtocol : public IWaylandProtocol {\n  public:\n    CViewporterProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CViewporterResource* resource);\n    void destroyResource(CViewportResource* resource);\n\n    //\n    std::vector<SP<CViewporterResource>> m_managers;\n    std::vector<SP<CViewportResource>>   m_viewports;\n\n    friend class CViewporterResource;\n    friend class CViewportResource;\n};\n\nnamespace PROTO {\n    inline UP<CViewporterProtocol> viewport;\n};\n"
  },
  {
    "path": "src/protocols/VirtualKeyboard.cpp",
    "content": "#include \"VirtualKeyboard.hpp\"\n#include <filesystem>\n#include <sys/mman.h>\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../helpers/time/Time.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\nusing namespace Hyprutils::OS;\n\nstatic std::string virtualKeyboardNameForWlClient(wl_client* client) {\n    std::string name = \"hl-virtual-keyboard\";\n\n    static auto PVKNAMEPROC = CConfigValue<Hyprlang::INT>(\"misc:name_vk_after_proc\");\n    if (!*PVKNAMEPROC)\n        return name;\n\n    name += \"-\";\n    const auto CLIENTNAME = binaryNameForWlClient(client);\n    if (CLIENTNAME.has_value()) {\n        const auto PATH = std::filesystem::path(CLIENTNAME.value());\n        if (PATH.has_filename()) {\n            const auto FILENAME = PATH.filename();\n            const auto NAME     = deviceNameToInternalString(FILENAME);\n            name += NAME;\n            return name;\n        }\n    }\n\n    name += \"unknown\";\n    return name;\n}\n\nCVirtualKeyboardV1Resource::CVirtualKeyboardV1Resource(SP<CZwpVirtualKeyboardV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwpVirtualKeyboardV1* r) { destroy(); });\n    m_resource->setOnDestroy([this](CZwpVirtualKeyboardV1* r) { destroy(); });\n\n    m_resource->setKey([this](CZwpVirtualKeyboardV1* r, uint32_t timeMs, uint32_t key, uint32_t state) {\n        if UNLIKELY (!m_hasKeymap) {\n            r->error(ZWP_VIRTUAL_KEYBOARD_V1_ERROR_NO_KEYMAP, \"Key event received before a keymap was set\");\n            return;\n        }\n\n        m_events.key.emit(IKeyboard::SKeyEvent{\n            .timeMs  = timeMs,\n            .keycode = key,\n            .state   = sc<wl_keyboard_key_state>(state),\n        });\n\n        const bool CONTAINS = std::ranges::contains(m_pressed, key);\n        if (state && !CONTAINS)\n            m_pressed.emplace_back(key);\n        else if (!state && CONTAINS)\n            std::erase(m_pressed, key);\n    });\n\n    m_resource->setModifiers([this](CZwpVirtualKeyboardV1* r, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {\n        if UNLIKELY (!m_hasKeymap) {\n            r->error(ZWP_VIRTUAL_KEYBOARD_V1_ERROR_NO_KEYMAP, \"Mods event received before a keymap was set\");\n            return;\n        }\n\n        m_events.modifiers.emit(IKeyboard::SModifiersEvent{\n            .depressed = depressed,\n            .latched   = latched,\n            .locked    = locked,\n            .group     = group,\n        });\n    });\n\n    m_resource->setKeymap([this](CZwpVirtualKeyboardV1* r, uint32_t fmt, int32_t fd, uint32_t len) {\n        auto            xkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);\n        CFileDescriptor keymapFd{fd};\n        if UNLIKELY (!xkbContext) {\n            LOGM(Log::ERR, \"xkbContext creation failed\");\n            r->noMemory();\n            return;\n        }\n\n        auto keymapData = mmap(nullptr, len, PROT_READ, MAP_PRIVATE, keymapFd.get(), 0);\n        if UNLIKELY (keymapData == MAP_FAILED) {\n            LOGM(Log::ERR, \"keymapData alloc failed\");\n            xkb_context_unref(xkbContext);\n            r->noMemory();\n            return;\n        }\n\n        auto xkbKeymap = xkb_keymap_new_from_string(xkbContext, sc<const char*>(keymapData), XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS);\n        munmap(keymapData, len);\n\n        if UNLIKELY (!xkbKeymap) {\n            LOGM(Log::ERR, \"xkbKeymap creation failed\");\n            xkb_context_unref(xkbContext);\n            r->noMemory();\n            return;\n        }\n\n        m_events.keymap.emit(IKeyboard::SKeymapEvent{\n            .keymap = xkbKeymap,\n        });\n        m_hasKeymap = true;\n\n        xkb_keymap_unref(xkbKeymap);\n        xkb_context_unref(xkbContext);\n    });\n\n    m_name = virtualKeyboardNameForWlClient(resource_->client());\n}\n\nCVirtualKeyboardV1Resource::~CVirtualKeyboardV1Resource() {\n    m_events.destroy.emit();\n}\n\nbool CVirtualKeyboardV1Resource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CVirtualKeyboardV1Resource::client() {\n    return m_resource->resource() ? m_resource->client() : nullptr;\n}\n\nvoid CVirtualKeyboardV1Resource::releasePressed() {\n    for (auto const& p : m_pressed) {\n        m_events.key.emit(IKeyboard::SKeyEvent{\n            .timeMs  = Time::millis(Time::steadyNow()),\n            .keycode = p,\n            .state   = WL_KEYBOARD_KEY_STATE_RELEASED,\n        });\n    }\n\n    m_pressed.clear();\n}\n\nvoid CVirtualKeyboardV1Resource::destroy() {\n    const auto RELEASEPRESSED = g_pConfigManager->getDeviceInt(m_name, \"release_pressed_on_close\", \"input:virtualkeyboard:release_pressed_on_close\");\n    if (RELEASEPRESSED)\n        releasePressed();\n    m_events.destroy.emit();\n    PROTO::virtualKeyboard->destroyResource(this);\n}\n\nCVirtualKeyboardProtocol::CVirtualKeyboardProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CVirtualKeyboardProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwpVirtualKeyboardManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwpVirtualKeyboardManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setCreateVirtualKeyboard([this](CZwpVirtualKeyboardManagerV1* pMgr, wl_resource* seat, uint32_t id) { this->onCreateKeeb(pMgr, seat, id); });\n}\n\nvoid CVirtualKeyboardProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CVirtualKeyboardProtocol::destroyResource(CVirtualKeyboardV1Resource* keeb) {\n    std::erase_if(m_keyboards, [&](const auto& other) { return other.get() == keeb; });\n}\n\nvoid CVirtualKeyboardProtocol::onCreateKeeb(CZwpVirtualKeyboardManagerV1* pMgr, wl_resource* seat, uint32_t id) {\n\n    const auto RESOURCE = m_keyboards.emplace_back(makeShared<CVirtualKeyboardV1Resource>(makeShared<CZwpVirtualKeyboardV1>(pMgr->client(), pMgr->version(), id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_keyboards.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New VKeyboard at id {}\", id);\n\n    m_events.newKeyboard.emit(RESOURCE);\n}\n"
  },
  {
    "path": "src/protocols/VirtualKeyboard.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"../devices/IKeyboard.hpp\"\n#include \"../devices/VirtualKeyboard.hpp\"\n#include \"virtual-keyboard-unstable-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CVirtualKeyboardV1Resource {\n  public:\n    CVirtualKeyboardV1Resource(SP<CZwpVirtualKeyboardV1> resource_);\n    ~CVirtualKeyboardV1Resource();\n\n    struct {\n        CSignalT<>                           destroy;\n        CSignalT<IKeyboard::SKeyEvent>       key;\n        CSignalT<IKeyboard::SModifiersEvent> modifiers;\n        CSignalT<IKeyboard::SKeymapEvent>    keymap;\n    } m_events;\n\n    bool        good();\n    wl_client*  client();\n\n    std::string m_name = \"\";\n\n  private:\n    SP<CZwpVirtualKeyboardV1> m_resource;\n\n    void                      releasePressed();\n    void                      destroy();\n\n    bool                      m_hasKeymap = false;\n\n    std::vector<uint32_t>     m_pressed;\n};\n\nclass CVirtualKeyboardProtocol : public IWaylandProtocol {\n  public:\n    CVirtualKeyboardProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<SP<CVirtualKeyboardV1Resource>> newKeyboard;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CVirtualKeyboardV1Resource* keeb);\n    void onCreateKeeb(CZwpVirtualKeyboardManagerV1* pMgr, wl_resource* seat, uint32_t id);\n\n    //\n    std::vector<UP<CZwpVirtualKeyboardManagerV1>> m_managers;\n    std::vector<SP<CVirtualKeyboardV1Resource>>   m_keyboards;\n\n    friend class CVirtualKeyboardV1Resource;\n};\n\nnamespace PROTO {\n    inline UP<CVirtualKeyboardProtocol> virtualKeyboard;\n};\n"
  },
  {
    "path": "src/protocols/VirtualPointer.cpp",
    "content": "#include \"VirtualPointer.hpp\"\n#include \"core/Output.hpp\"\n\nCVirtualPointerV1Resource::CVirtualPointerV1Resource(SP<CZwlrVirtualPointerV1> resource_, PHLMONITORREF boundOutput_) : m_boundOutput(boundOutput_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CZwlrVirtualPointerV1* r) {\n        m_events.destroy.emit();\n        PROTO::virtualPointer->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CZwlrVirtualPointerV1* r) {\n        m_events.destroy.emit();\n        PROTO::virtualPointer->destroyResource(this);\n    });\n\n    m_resource->setMotion([this](CZwlrVirtualPointerV1* r, uint32_t timeMs, wl_fixed_t dx, wl_fixed_t dy) {\n        m_events.move.emit(IPointer::SMotionEvent{\n            .timeMs  = timeMs,\n            .delta   = {wl_fixed_to_double(dx), wl_fixed_to_double(dy)},\n            .unaccel = {wl_fixed_to_double(dx), wl_fixed_to_double(dy)},\n            .device  = nullptr,\n        });\n    });\n\n    m_resource->setMotionAbsolute([this](CZwlrVirtualPointerV1* r, uint32_t timeMs, uint32_t x, uint32_t y, uint32_t xExtent, uint32_t yExtent) {\n        if (!xExtent || !yExtent)\n            return;\n\n        m_events.warp.emit(IPointer::SMotionAbsoluteEvent{\n            .timeMs   = timeMs,\n            .absolute = {sc<double>(x) / xExtent, sc<double>(y) / yExtent},\n        });\n    });\n\n    m_resource->setButton([this](CZwlrVirtualPointerV1* r, uint32_t timeMs, uint32_t button, uint32_t state) {\n        m_events.button.emit(IPointer::SButtonEvent{\n            .timeMs = timeMs,\n            .button = button,\n            .state  = sc<wl_pointer_button_state>(state),\n        });\n    });\n\n    m_resource->setAxis([this](CZwlrVirtualPointerV1* r, uint32_t timeMs, uint32_t axis_, wl_fixed_t value) {\n        if UNLIKELY (m_axis > WL_POINTER_AXIS_HORIZONTAL_SCROLL) {\n            r->error(ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS, \"Invalid axis\");\n            return;\n        }\n\n        m_axis               = axis_;\n        m_axisEvents[m_axis] = IPointer::SAxisEvent{.timeMs = timeMs, .axis = sc<wl_pointer_axis>(m_axis), .delta = wl_fixed_to_double(value)};\n    });\n\n    m_resource->setFrame([this](CZwlrVirtualPointerV1* r) {\n        for (auto& e : m_axisEvents) {\n            if (!e.timeMs)\n                continue;\n            m_events.axis.emit(e);\n            e.timeMs = 0;\n        }\n\n        m_events.frame.emit();\n    });\n\n    m_resource->setAxisSource([this](CZwlrVirtualPointerV1* r, uint32_t source) { m_axisEvents[m_axis].source = sc<wl_pointer_axis_source>(source); });\n\n    m_resource->setAxisStop([this](CZwlrVirtualPointerV1* r, uint32_t timeMs, uint32_t axis_) {\n        if UNLIKELY (m_axis > WL_POINTER_AXIS_HORIZONTAL_SCROLL) {\n            r->error(ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS, \"Invalid axis\");\n            return;\n        }\n\n        m_axis                             = axis_;\n        m_axisEvents[m_axis].timeMs        = timeMs;\n        m_axisEvents[m_axis].axis          = sc<wl_pointer_axis>(m_axis);\n        m_axisEvents[m_axis].delta         = 0;\n        m_axisEvents[m_axis].deltaDiscrete = 0;\n    });\n\n    m_resource->setAxisDiscrete([this](CZwlrVirtualPointerV1* r, uint32_t timeMs, uint32_t axis_, wl_fixed_t value, int32_t discrete) {\n        if UNLIKELY (m_axis > WL_POINTER_AXIS_HORIZONTAL_SCROLL) {\n            r->error(ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS, \"Invalid axis\");\n            return;\n        }\n\n        m_axis                             = axis_;\n        m_axisEvents[m_axis].timeMs        = timeMs;\n        m_axisEvents[m_axis].axis          = sc<wl_pointer_axis>(m_axis);\n        m_axisEvents[m_axis].delta         = wl_fixed_to_double(value);\n        m_axisEvents[m_axis].deltaDiscrete = discrete * 120;\n    });\n}\n\nCVirtualPointerV1Resource::~CVirtualPointerV1Resource() {\n    m_events.destroy.emit();\n}\n\nbool CVirtualPointerV1Resource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CVirtualPointerV1Resource::client() {\n    return m_resource->client();\n}\n\nCVirtualPointerProtocol::CVirtualPointerProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CVirtualPointerProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZwlrVirtualPointerManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZwlrVirtualPointerManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n    RESOURCE->setDestroy([this](CZwlrVirtualPointerManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setCreateVirtualPointer([this](CZwlrVirtualPointerManagerV1* pMgr, wl_resource* seat, uint32_t id) { this->onCreatePointer(pMgr, seat, id, {}); });\n    RESOURCE->setCreateVirtualPointerWithOutput([this](CZwlrVirtualPointerManagerV1* pMgr, wl_resource* seat, wl_resource* output, uint32_t id) {\n        if (output) {\n            auto RES = CWLOutputResource::fromResource(output);\n            if (!RES) {\n                this->onCreatePointer(pMgr, seat, id, {});\n                return;\n            }\n\n            this->onCreatePointer(pMgr, seat, id, RES->m_monitor);\n        } else\n            this->onCreatePointer(pMgr, seat, id, {});\n    });\n}\n\nvoid CVirtualPointerProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CVirtualPointerProtocol::destroyResource(CVirtualPointerV1Resource* pointer) {\n    std::erase_if(m_pointers, [&](const auto& other) { return other.get() == pointer; });\n}\n\nvoid CVirtualPointerProtocol::onCreatePointer(CZwlrVirtualPointerManagerV1* pMgr, wl_resource* seat, uint32_t id, PHLMONITORREF output) {\n\n    const auto RESOURCE = m_pointers.emplace_back(makeShared<CVirtualPointerV1Resource>(makeShared<CZwlrVirtualPointerV1>(pMgr->client(), pMgr->version(), id), output));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_pointers.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New VPointer at id {}\", id);\n\n    m_events.newPointer.emit(RESOURCE);\n}\n"
  },
  {
    "path": "src/protocols/VirtualPointer.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include <array>\n#include \"WaylandProtocol.hpp\"\n#include \"wlr-virtual-pointer-unstable-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../devices/IPointer.hpp\"\n#include \"../helpers/Monitor.hpp\"\n\nclass CVirtualPointerV1Resource {\n  public:\n    CVirtualPointerV1Resource(SP<CZwlrVirtualPointerV1> resource_, PHLMONITORREF boundOutput_);\n    ~CVirtualPointerV1Resource();\n\n    struct {\n        CSignalT<>                               destroy;\n        CSignalT<IPointer::SMotionEvent>         move;\n        CSignalT<IPointer::SMotionAbsoluteEvent> warp;\n        CSignalT<IPointer::SButtonEvent>         button;\n        CSignalT<IPointer::SAxisEvent>           axis;\n        CSignalT<>                               frame;\n\n        CSignalT<IPointer::SSwipeBeginEvent>     swipeBegin;\n        CSignalT<IPointer::SSwipeUpdateEvent>    swipeUpdate;\n        CSignalT<IPointer::SSwipeEndEvent>       swipeEnd;\n\n        CSignalT<IPointer::SPinchBeginEvent>     pinchBegin;\n        CSignalT<IPointer::SPinchUpdateEvent>    pinchUpdate;\n        CSignalT<IPointer::SPinchEndEvent>       pinchEnd;\n\n        CSignalT<IPointer::SHoldBeginEvent>      holdBegin;\n        CSignalT<IPointer::SHoldEndEvent>        holdEnd;\n    } m_events;\n\n    bool          good();\n    wl_client*    client();\n\n    std::string   m_name;\n\n    PHLMONITORREF m_boundOutput;\n\n  private:\n    SP<CZwlrVirtualPointerV1>           m_resource;\n\n    uint32_t                            m_axis = 0;\n\n    std::array<IPointer::SAxisEvent, 2> m_axisEvents;\n};\n\nclass CVirtualPointerProtocol : public IWaylandProtocol {\n  public:\n    CVirtualPointerProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<SP<CVirtualPointerV1Resource>> newPointer;\n    } m_events;\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CVirtualPointerV1Resource* pointer);\n    void onCreatePointer(CZwlrVirtualPointerManagerV1* pMgr, wl_resource* seat, uint32_t id, PHLMONITORREF output);\n\n    //\n    std::vector<UP<CZwlrVirtualPointerManagerV1>> m_managers;\n    std::vector<SP<CVirtualPointerV1Resource>>    m_pointers;\n\n    friend class CVirtualPointerV1Resource;\n};\n\nnamespace PROTO {\n    inline UP<CVirtualPointerProtocol> virtualPointer;\n};\n"
  },
  {
    "path": "src/protocols/WaylandProtocol.cpp",
    "content": "#include \"WaylandProtocol.hpp\"\n#include \"../Compositor.hpp\"\n\nstatic void bindManagerInternal(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    sc<IWaylandProtocol*>(data)->bindManager(client, data, ver, id);\n}\n\nstatic void displayDestroyInternal(struct wl_listener* listener, void* data) {\n    SIWaylandProtocolDestroyWrapper* wrap  = wl_container_of(listener, wrap, listener);\n    IWaylandProtocol*                proto = wrap->parent;\n    proto->onDisplayDestroy();\n}\n\nvoid IWaylandProtocol::onDisplayDestroy() {\n    wl_list_remove(&m_liDisplayDestroy.listener.link);\n    wl_list_init(&m_liDisplayDestroy.listener.link);\n    if (m_global) {\n        wl_global_destroy(m_global);\n        m_global = nullptr;\n    }\n}\n\nIWaylandProtocol::IWaylandProtocol(const wl_interface* iface, const int& ver, const std::string& name) :\n    m_name(name), m_global(wl_global_create(g_pCompositor->m_wlDisplay, iface, ver, this, &bindManagerInternal)) {\n\n    if UNLIKELY (!m_global) {\n        LOGM(Log::ERR, \"could not create a global [{}]\", m_name);\n        return;\n    }\n\n    wl_list_init(&m_liDisplayDestroy.listener.link);\n    m_liDisplayDestroy.listener.notify = displayDestroyInternal;\n    m_liDisplayDestroy.parent          = this;\n    wl_display_add_destroy_listener(g_pCompositor->m_wlDisplay, &m_liDisplayDestroy.listener);\n\n    LOGM(Log::DEBUG, \"Registered global [{}]\", m_name);\n}\n\nIWaylandProtocol::~IWaylandProtocol() {\n    onDisplayDestroy();\n}\n\nvoid IWaylandProtocol::removeGlobal() {\n    if (m_global)\n        wl_global_remove(m_global);\n}\n\nwl_global* IWaylandProtocol::getGlobal() {\n    return m_global;\n}\n"
  },
  {
    "path": "src/protocols/WaylandProtocol.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n\n#include <functional>\n#include <sstream>\n\n#define RESOURCE_OR_BAIL(resname)                                                                                                                                                  \\\n    const auto resname = (CWaylandResource*)wl_resource_get_user_data(resource);                                                                                                   \\\n    if (!resname)                                                                                                                                                                  \\\n        return;\n\n#define PROTO NProtocols\n\n#define EXTRACT_CLASS_NAME()                                                                                                                                                       \\\n    []() constexpr -> std::string_view {                                                                                                                                           \\\n        constexpr std::string_view prettyFunction = __PRETTY_FUNCTION__;                                                                                                           \\\n        constexpr size_t           colons         = prettyFunction.find(\"::\");                                                                                                     \\\n        if (colons != std::string_view::npos) {                                                                                                                                    \\\n            constexpr size_t begin = prettyFunction.substr(0, colons).rfind(' ') + 1;                                                                                              \\\n            constexpr size_t end   = colons - begin;                                                                                                                               \\\n            return prettyFunction.substr(begin, end);                                                                                                                              \\\n        } else {                                                                                                                                                                   \\\n            return \"Global\";                                                                                                                                                       \\\n        }                                                                                                                                                                          \\\n    }()\n\n#define LOGM(level, ...)                                                                                                                                                           \\\n    do {                                                                                                                                                                           \\\n        std::ostringstream oss;                                                                                                                                                    \\\n        if (level == Log::WARN || level == Log::ERR || level == Log::CRIT) {                                                                                                       \\\n            oss << \"[\" << __FILE__ << \":\" << __LINE__ << \"] \";                                                                                                                     \\\n        } else if (level == Log::DEBUG || level == Log::INFO || level == Log::TRACE) {                                                                                             \\\n            oss << \"[\" << EXTRACT_CLASS_NAME() << \"] \";                                                                                                                            \\\n        }                                                                                                                                                                          \\\n        if constexpr (std::tuple_size_v<decltype(std::make_tuple(__VA_ARGS__))> == 1 && std::is_same_v<decltype(__VA_ARGS__), std::string>) {                                      \\\n            oss << __VA_ARGS__;                                                                                                                                                    \\\n            Log::logger->log(level, oss.str());                                                                                                                                    \\\n        } else {                                                                                                                                                                   \\\n            Log::logger->log(level, std::format(\"{}{}\", oss.str(), std::format(__VA_ARGS__)));                                                                                     \\\n        }                                                                                                                                                                          \\\n    } while (0)\n\nclass IWaylandProtocol;\nstruct SIWaylandProtocolDestroyWrapper {\n    wl_listener       listener;\n    IWaylandProtocol* parent = nullptr;\n};\n\nclass IWaylandProtocol {\n  public:\n    IWaylandProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n    virtual ~IWaylandProtocol();\n\n    virtual void                    onDisplayDestroy();\n    virtual void                    removeGlobal();\n    virtual wl_global*              getGlobal();\n\n    virtual void                    bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) = 0;\n\n    SIWaylandProtocolDestroyWrapper m_liDisplayDestroy;\n\n  private:\n    std::string m_name;\n    wl_global*  m_global = nullptr;\n};\n"
  },
  {
    "path": "src/protocols/XDGActivation.cpp",
    "content": "#include \"XDGActivation.hpp\"\n#include \"../managers/TokenManager.hpp\"\n#include \"../Compositor.hpp\"\n#include \"core/Compositor.hpp\"\n#include <algorithm>\n\nCXDGActivationToken::CXDGActivationToken(SP<CXdgActivationTokenV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!resource_->resource())\n        return;\n\n    m_resource->setDestroy([this](CXdgActivationTokenV1* r) { PROTO::activation->destroyToken(this); });\n    m_resource->setOnDestroy([this](CXdgActivationTokenV1* r) { PROTO::activation->destroyToken(this); });\n\n    m_resource->setSetSerial([this](CXdgActivationTokenV1* r, uint32_t serial_, wl_resource* seat) { m_serial = serial_; });\n\n    m_resource->setSetAppId([this](CXdgActivationTokenV1* r, const char* appid) { m_appID = appid; });\n\n    m_resource->setCommit([this](CXdgActivationTokenV1* r) {\n        // TODO: should we send a protocol error of already_used here\n        // if it was used? the protocol spec doesn't say _when_ it should be sent...\n        if UNLIKELY (m_committed) {\n            LOGM(Log::WARN, \"possible protocol error, two commits from one token. Ignoring.\");\n            return;\n        }\n\n        m_committed = true;\n        // send done with a new token\n        m_token = g_pTokenManager->registerNewToken({}, std::chrono::months{12});\n\n        LOGM(Log::DEBUG, \"assigned new xdg-activation token {}\", m_token);\n\n        m_resource->sendDone(m_token.c_str());\n\n        PROTO::activation->m_sentTokens.push_back({m_token, m_resource->client()});\n\n        auto count = std::ranges::count_if(PROTO::activation->m_sentTokens, [this](const auto& other) { return other.client == m_resource->client(); });\n\n        if UNLIKELY (count > 10) {\n            // remove first token. Too many, dear app.\n            for (auto i = PROTO::activation->m_sentTokens.begin(); i != PROTO::activation->m_sentTokens.end(); ++i) {\n                if (i->client == m_resource->client()) {\n                    PROTO::activation->m_sentTokens.erase(i);\n                    break;\n                }\n            }\n        }\n    });\n}\n\nCXDGActivationToken::~CXDGActivationToken() {\n    if (m_committed)\n        g_pTokenManager->removeToken(g_pTokenManager->getToken(m_token));\n}\n\nbool CXDGActivationToken::good() {\n    return m_resource->resource();\n}\n\nCXDGActivationProtocol::CXDGActivationProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CXDGActivationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CXdgActivationV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CXdgActivationV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CXdgActivationV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetActivationToken([this](CXdgActivationV1* pMgr, uint32_t id) { this->onGetToken(pMgr, id); });\n    RESOURCE->setActivate([this](CXdgActivationV1* pMgr, const char* token, wl_resource* surface) {\n        auto TOKEN = std::ranges::find_if(m_sentTokens, [token](const auto& t) { return t.token == token; });\n\n        if UNLIKELY (TOKEN == m_sentTokens.end()) {\n            LOGM(Log::WARN, \"activate event for non-existent token {}??\", token);\n            return;\n        }\n\n        // remove token. It's been now spent.\n        m_sentTokens.erase(TOKEN);\n\n        SP<CWLSurfaceResource> surf    = CWLSurfaceResource::fromResource(surface);\n        const auto             PWINDOW = g_pCompositor->getWindowFromSurface(surf);\n\n        if UNLIKELY (!PWINDOW) {\n            LOGM(Log::WARN, \"activate event for non-window or gone surface with token {}, ignoring\", token);\n            return;\n        }\n\n        PWINDOW->activate();\n    });\n}\n\nvoid CXDGActivationProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CXDGActivationProtocol::destroyToken(CXDGActivationToken* token) {\n    std::erase_if(m_tokens, [&](const auto& other) { return other.get() == token; });\n}\n\nvoid CXDGActivationProtocol::onGetToken(CXdgActivationV1* pMgr, uint32_t id) {\n    const auto CLIENT   = pMgr->client();\n    const auto RESOURCE = m_tokens.emplace_back(makeUnique<CXDGActivationToken>(makeShared<CXdgActivationTokenV1>(CLIENT, pMgr->version(), id))).get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_tokens.pop_back();\n        return;\n    }\n}"
  },
  {
    "path": "src/protocols/XDGActivation.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"xdg-activation-v1.hpp\"\n\nclass CXDGActivationToken {\n  public:\n    CXDGActivationToken(SP<CXdgActivationTokenV1> resource_);\n    ~CXDGActivationToken();\n\n    bool good();\n\n  private:\n    SP<CXdgActivationTokenV1> m_resource;\n\n    uint32_t                  m_serial    = 0;\n    std::string               m_appID     = \"\";\n    bool                      m_committed = false;\n\n    std::string               m_token = \"\";\n\n    friend class CXDGActivationProtocol;\n};\n\nclass CXDGActivationProtocol : public IWaylandProtocol {\n  public:\n    CXDGActivationProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyToken(CXDGActivationToken* pointer);\n    void onGetToken(CXdgActivationV1* pMgr, uint32_t id);\n\n    struct SSentToken {\n        std::string token;\n        wl_client*  client = nullptr; // READ-ONLY: can be dead\n    };\n    std::vector<SSentToken> m_sentTokens;\n\n    //\n    std::vector<UP<CXdgActivationV1>>    m_managers;\n    std::vector<UP<CXDGActivationToken>> m_tokens;\n\n    friend class CXDGActivationToken;\n};\n\nnamespace PROTO {\n    inline UP<CXDGActivationProtocol> activation;\n};\n"
  },
  {
    "path": "src/protocols/XDGBell.cpp",
    "content": "#include \"XDGBell.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"../managers/EventManager.hpp\"\n#include \"../Compositor.hpp\"\n\nCXDGSystemBellManagerResource::CXDGSystemBellManagerResource(UP<CXdgSystemBellV1>&& resource) : m_resource(std::move(resource)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXdgSystemBellV1* r) { PROTO::xdgBell->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXdgSystemBellV1* r) { PROTO::xdgBell->destroyResource(this); });\n\n    m_resource->setRing([](CXdgSystemBellV1* r, wl_resource* surface) {\n        if (!surface) {\n            g_pEventManager->postEvent(SHyprIPCEvent{\n                .event = \"bell\",\n                .data  = \"\",\n            });\n            return;\n        }\n\n        const auto SURFACE = CWLSurfaceResource::fromResource(surface);\n\n        if (!SURFACE) {\n            g_pEventManager->postEvent(SHyprIPCEvent{\n                .event = \"bell\",\n                .data  = \"\",\n            });\n            return;\n        }\n\n        for (const auto& w : g_pCompositor->m_windows) {\n            if (!w->m_isMapped || w->m_isX11 || !w->m_xdgSurface || !w->wlSurface())\n                continue;\n\n            if (w->wlSurface()->resource() == SURFACE) {\n                g_pEventManager->postEvent(SHyprIPCEvent{\n                    .event = \"bell\",\n                    .data  = std::format(\"{:x}\", rc<uintptr_t>(w.get())),\n                });\n                return;\n            }\n        }\n\n        g_pEventManager->postEvent(SHyprIPCEvent{\n            .event = \"bell\",\n            .data  = \"\",\n        });\n    });\n}\n\nbool CXDGSystemBellManagerResource::good() {\n    return m_resource->resource();\n}\n\nCXDGSystemBellProtocol::CXDGSystemBellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CXDGSystemBellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = WP<CXDGSystemBellManagerResource>{m_managers.emplace_back(makeUnique<CXDGSystemBellManagerResource>(makeUnique<CXdgSystemBellV1>(client, ver, id)))};\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        return;\n    }\n}\n\nvoid CXDGSystemBellProtocol::destroyResource(CXDGSystemBellManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n"
  },
  {
    "path": "src/protocols/XDGBell.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"xdg-system-bell-v1.hpp\"\n\nclass CXDGSystemBellManagerResource {\n  public:\n    CXDGSystemBellManagerResource(UP<CXdgSystemBellV1>&& resource);\n\n    bool good();\n\n  private:\n    UP<CXdgSystemBellV1> m_resource;\n};\n\nclass CXDGSystemBellProtocol : public IWaylandProtocol {\n  public:\n    CXDGSystemBellProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CXDGSystemBellManagerResource* res);\n\n    //\n    std::vector<UP<CXDGSystemBellManagerResource>> m_managers;\n\n    friend class CXDGSystemBellManagerResource;\n};\n\nnamespace PROTO {\n    inline UP<CXDGSystemBellProtocol> xdgBell;\n};\n"
  },
  {
    "path": "src/protocols/XDGDecoration.cpp",
    "content": "#include \"XDGDecoration.hpp\"\n#include <algorithm>\n\nCXDGDecoration::CXDGDecoration(SP<CZxdgToplevelDecorationV1> resource_, wl_resource* toplevel) : m_resource(resource_), m_toplevelResource(toplevel) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([this](CZxdgToplevelDecorationV1* pMgr) { PROTO::xdgDecoration->destroyDecoration(this); });\n    m_resource->setOnDestroy([this](CZxdgToplevelDecorationV1* pMgr) { PROTO::xdgDecoration->destroyDecoration(this); });\n\n    m_resource->setSetMode([this](CZxdgToplevelDecorationV1*, zxdgToplevelDecorationV1Mode mode) {\n        std::string modeString;\n        switch (mode) {\n            case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: modeString = \"MODE_CLIENT_SIDE\"; break;\n            case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: modeString = \"MODE_SERVER_SIDE\"; break;\n            default: modeString = \"INVALID\"; break;\n        }\n\n        LOGM(Log::DEBUG, \"setMode: {}. {} MODE_SERVER_SIDE as reply.\", modeString, (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE ? \"Sending\" : \"Ignoring and sending\"));\n        auto sendMode = xdgModeOnRequestCSD(mode);\n        m_resource->sendConfigure(sendMode);\n        mostRecentlySent      = sendMode;\n        mostRecentlyRequested = mode;\n    });\n\n    m_resource->setUnsetMode([this](CZxdgToplevelDecorationV1*) {\n        LOGM(Log::DEBUG, \"unsetMode. Sending MODE_SERVER_SIDE.\");\n        auto sendMode = xdgModeOnReleaseCSD();\n        m_resource->sendConfigure(sendMode);\n        mostRecentlySent      = sendMode;\n        mostRecentlyRequested = 0;\n    });\n\n    auto sendMode = xdgDefaultModeCSD();\n    m_resource->sendConfigure(sendMode);\n    mostRecentlySent = sendMode;\n}\n\nzxdgToplevelDecorationV1Mode CXDGDecoration::xdgDefaultModeCSD() {\n    return ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;\n}\n\nzxdgToplevelDecorationV1Mode CXDGDecoration::xdgModeOnRequestCSD(uint32_t modeRequestedByClient) {\n    return xdgDefaultModeCSD();\n}\n\nzxdgToplevelDecorationV1Mode CXDGDecoration::xdgModeOnReleaseCSD() {\n    return xdgDefaultModeCSD();\n}\n\nbool CXDGDecoration::good() {\n    return m_resource->resource();\n}\n\nwl_resource* CXDGDecoration::toplevelResource() {\n    return m_toplevelResource;\n}\n\nCXDGDecorationProtocol::CXDGDecorationProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CXDGDecorationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeUnique<CZxdgDecorationManagerV1>(client, ver, id)).get();\n    RESOURCE->setOnDestroy([this](CZxdgDecorationManagerV1* p) { this->onManagerResourceDestroy(p->resource()); });\n\n    RESOURCE->setDestroy([this](CZxdgDecorationManagerV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });\n    RESOURCE->setGetToplevelDecoration([this](CZxdgDecorationManagerV1* pMgr, uint32_t id, wl_resource* xdgToplevel) { this->onGetDecoration(pMgr, id, xdgToplevel); });\n}\n\nvoid CXDGDecorationProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CXDGDecorationProtocol::destroyDecoration(CXDGDecoration* decoration) {\n    m_decorations.erase(decoration->toplevelResource());\n}\n\nvoid CXDGDecorationProtocol::onGetDecoration(CZxdgDecorationManagerV1* pMgr, uint32_t id, wl_resource* xdgToplevel) {\n    if UNLIKELY (m_decorations.contains(xdgToplevel)) {\n        pMgr->error(ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED, \"Decoration object already exists\");\n        return;\n    }\n\n    const auto CLIENT = pMgr->client();\n    const auto RESOURCE =\n        m_decorations.emplace(xdgToplevel, makeUnique<CXDGDecoration>(makeShared<CZxdgToplevelDecorationV1>(CLIENT, pMgr->version(), id), xdgToplevel)).first->second.get();\n\n    if UNLIKELY (!RESOURCE->good()) {\n        pMgr->noMemory();\n        m_decorations.erase(xdgToplevel);\n        return;\n    }\n}\n"
  },
  {
    "path": "src/protocols/XDGDecoration.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"xdg-decoration-unstable-v1.hpp\"\n\nclass CXDGDecoration {\n  public:\n    CXDGDecoration(SP<CZxdgToplevelDecorationV1> resource_, wl_resource* toplevel);\n\n    uint32_t     mostRecentlySent      = 0;\n    uint32_t     mostRecentlyRequested = 0;\n\n    bool         good();\n    wl_resource* toplevelResource();\n\n  private:\n    zxdgToplevelDecorationV1Mode  xdgDefaultModeCSD();\n    zxdgToplevelDecorationV1Mode  xdgModeOnRequestCSD(uint32_t modeRequestedByClient);\n    zxdgToplevelDecorationV1Mode  xdgModeOnReleaseCSD();\n\n    SP<CZxdgToplevelDecorationV1> m_resource;\n    wl_resource*                  m_toplevelResource = nullptr; // READ-ONLY.\n};\n\nclass CXDGDecorationProtocol : public IWaylandProtocol {\n  public:\n    CXDGDecorationProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyDecoration(CXDGDecoration* decoration);\n    void onGetDecoration(CZxdgDecorationManagerV1* pMgr, uint32_t id, wl_resource* xdgToplevel);\n\n    //\n    std::vector<UP<CZxdgDecorationManagerV1>>            m_managers;\n    std::unordered_map<wl_resource*, UP<CXDGDecoration>> m_decorations; // xdg_toplevel -> deco\n\n    friend class CXDGDecoration;\n};\n\nnamespace PROTO {\n    inline UP<CXDGDecorationProtocol> xdgDecoration;\n};\n"
  },
  {
    "path": "src/protocols/XDGDialog.cpp",
    "content": "#include \"XDGDialog.hpp\"\n#include \"XDGShell.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../Compositor.hpp\"\n#include <algorithm>\n\nCXDGDialogV1Resource::CXDGDialogV1Resource(SP<CXdgDialogV1> resource_, SP<CXDGToplevelResource> toplevel_) : m_resource(resource_), m_toplevel(toplevel_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXdgDialogV1* r) { PROTO::xdgDialog->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXdgDialogV1* r) { PROTO::xdgDialog->destroyResource(this); });\n\n    m_resource->setSetModal([this](CXdgDialogV1* r) {\n        modal = true;\n        updateWindow();\n    });\n\n    m_resource->setUnsetModal([this](CXdgDialogV1* r) {\n        modal = false;\n        updateWindow();\n    });\n}\n\nvoid CXDGDialogV1Resource::updateWindow() {\n    if UNLIKELY (!m_toplevel || !m_toplevel->m_parent || !m_toplevel->m_parent->m_owner)\n        return;\n\n    const auto HLSURFACE = Desktop::View::CWLSurface::fromResource(m_toplevel->m_parent->m_owner->m_surface.lock());\n    if UNLIKELY (!HLSURFACE)\n        return;\n\n    const auto WINDOW = Desktop::View::CWindow::fromView(HLSURFACE->view());\n\n    if UNLIKELY (!WINDOW)\n        return;\n\n    WINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_MODAL);\n}\n\nbool CXDGDialogV1Resource::good() {\n    return m_resource->resource();\n}\n\nCXDGWmDialogManagerResource::CXDGWmDialogManagerResource(SP<CXdgWmDialogV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXdgWmDialogV1* r) { PROTO::xdgDialog->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXdgWmDialogV1* r) { PROTO::xdgDialog->destroyResource(this); });\n\n    m_resource->setGetXdgDialog([](CXdgWmDialogV1* r, uint32_t id, wl_resource* toplevel) {\n        auto tl = CXDGToplevelResource::fromResource(toplevel);\n        if UNLIKELY (!tl) {\n            r->error(-1, \"Toplevel inert\");\n            return;\n        }\n\n        const auto RESOURCE = PROTO::xdgDialog->m_dialogs.emplace_back(makeShared<CXDGDialogV1Resource>(makeShared<CXdgDialogV1>(r->client(), r->version(), id), tl));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            return;\n        }\n\n        tl->m_dialog = RESOURCE;\n    });\n}\n\nbool CXDGWmDialogManagerResource::good() {\n    return m_resource->resource();\n}\n\nCXDGDialogProtocol::CXDGDialogProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CXDGDialogProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CXDGWmDialogManagerResource>(makeShared<CXdgWmDialogV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        return;\n    }\n}\n\nvoid CXDGDialogProtocol::destroyResource(CXDGWmDialogManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n\nvoid CXDGDialogProtocol::destroyResource(CXDGDialogV1Resource* res) {\n    std::erase_if(m_dialogs, [&](const auto& other) { return other.get() == res; });\n}\n"
  },
  {
    "path": "src/protocols/XDGDialog.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <unordered_map>\n#include \"WaylandProtocol.hpp\"\n#include \"xdg-dialog-v1.hpp\"\n\nclass CXDGToplevelResource;\n\nclass CXDGDialogV1Resource {\n  public:\n    CXDGDialogV1Resource(SP<CXdgDialogV1> resource_, SP<CXDGToplevelResource> toplevel_);\n\n    bool good();\n\n    bool modal = false;\n\n  private:\n    SP<CXdgDialogV1>         m_resource;\n    WP<CXDGToplevelResource> m_toplevel;\n\n    void                     updateWindow();\n};\n\nclass CXDGWmDialogManagerResource {\n  public:\n    CXDGWmDialogManagerResource(SP<CXdgWmDialogV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CXdgWmDialogV1> m_resource;\n};\n\nclass CXDGDialogProtocol : public IWaylandProtocol {\n  public:\n    CXDGDialogProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void destroyResource(CXDGWmDialogManagerResource* res);\n    void destroyResource(CXDGDialogV1Resource* res);\n\n    //\n    std::vector<SP<CXDGWmDialogManagerResource>> m_managers;\n    std::vector<SP<CXDGDialogV1Resource>>        m_dialogs;\n\n    friend class CXDGWmDialogManagerResource;\n    friend class CXDGDialogV1Resource;\n};\n\nnamespace PROTO {\n    inline UP<CXDGDialogProtocol> xdgDialog;\n};\n"
  },
  {
    "path": "src/protocols/XDGOutput.cpp",
    "content": "#include \"XDGOutput.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../xwayland/XWayland.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"core/Output.hpp\"\n\n#define OUTPUT_MANAGER_VERSION                   3\n#define OUTPUT_DONE_DEPRECATED_SINCE_VERSION     3\n#define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3\n#define OUTPUT_NAME_SINCE_VERSION                2\n#define OUTPUT_DESCRIPTION_SINCE_VERSION         2\n\n//\n\nvoid CXDGOutputProtocol::onManagerResourceDestroy(wl_resource* res) {\n    std::erase_if(m_managerResources, [&](const auto& other) { return other->resource() == res; });\n}\n\nvoid CXDGOutputProtocol::onOutputResourceDestroy(wl_resource* res) {\n    std::erase_if(m_xdgOutputs, [&](const auto& other) { return other->m_resource->resource() == res; });\n}\n\nvoid CXDGOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managerResources.emplace_back(makeUnique<CZxdgOutputManagerV1>(client, ver, id)).get();\n\n    if UNLIKELY (!RESOURCE->resource()) {\n        LOGM(Log::DEBUG, \"Couldn't bind XDGOutputMgr\");\n        wl_client_post_no_memory(client);\n        return;\n    }\n\n    RESOURCE->setDestroy([this](CZxdgOutputManagerV1* res) { onManagerResourceDestroy(res->resource()); });\n    RESOURCE->setOnDestroy([this](CZxdgOutputManagerV1* res) { onManagerResourceDestroy(res->resource()); });\n    RESOURCE->setGetXdgOutput([this](CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* output) { onManagerGetXDGOutput(mgr, id, output); });\n}\n\nCXDGOutputProtocol::CXDGOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    static auto P  = Event::bus()->m_events.monitor.layoutChanged.listen([this] { updateAllOutputs(); });\n    static auto P2 = Event::bus()->m_events.config.reloaded.listen([this] { updateAllOutputs(); });\n}\n\nvoid CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* outputResource) {\n    const auto  OUTPUT   = CWLOutputResource::fromResource(outputResource);\n    const auto  PMONITOR = OUTPUT->m_monitor.lock();\n    const auto  CLIENT   = mgr->client();\n\n    CXDGOutput* pXDGOutput = m_xdgOutputs.emplace_back(makeUnique<CXDGOutput>(makeShared<CZxdgOutputV1>(CLIENT, mgr->version(), id), PMONITOR)).get();\n#ifndef NO_XWAYLAND\n    if (g_pXWayland && g_pXWayland->m_server && g_pXWayland->m_server->m_xwaylandClient == CLIENT)\n        pXDGOutput->m_isXWayland = true;\n#endif\n    pXDGOutput->m_client = CLIENT;\n\n    pXDGOutput->m_outputProto = OUTPUT->m_owner;\n\n    if UNLIKELY (!pXDGOutput->m_resource->resource()) {\n        m_xdgOutputs.pop_back();\n        mgr->noMemory();\n        return;\n    }\n\n    if UNLIKELY (!PMONITOR) {\n        LOGM(Log::ERR, \"New xdg_output from client {:x} ({}) has no CMonitor?!\", (uintptr_t)CLIENT, pXDGOutput->m_isXWayland ? \"xwayland\" : \"not xwayland\");\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New xdg_output for {}: client {:x} ({})\", PMONITOR->m_name, (uintptr_t)CLIENT, pXDGOutput->m_isXWayland ? \"xwayland\" : \"not xwayland\");\n\n    const auto XDGVER = pXDGOutput->m_resource->version();\n\n    if (XDGVER >= OUTPUT_NAME_SINCE_VERSION)\n        pXDGOutput->m_resource->sendName(PMONITOR->m_name.c_str());\n    if (XDGVER >= OUTPUT_DESCRIPTION_SINCE_VERSION && !PMONITOR->m_output->description.empty())\n        pXDGOutput->m_resource->sendDescription(PMONITOR->m_output->description.c_str());\n\n    pXDGOutput->sendDetails();\n\n    const auto OUTPUTVER = wl_resource_get_version(outputResource);\n    if (OUTPUTVER >= WL_OUTPUT_DONE_SINCE_VERSION && XDGVER >= OUTPUT_DONE_DEPRECATED_SINCE_VERSION)\n        wl_output_send_done(outputResource);\n}\n\nvoid CXDGOutputProtocol::updateAllOutputs() {\n    LOGM(Log::DEBUG, \"updating all xdg_output heads\");\n\n    for (auto const& o : m_xdgOutputs) {\n        if (!o->m_monitor)\n            continue;\n\n        o->sendDetails();\n\n        o->m_monitor->scheduleDone();\n    }\n}\n\n//\n\nCXDGOutput::CXDGOutput(SP<CZxdgOutputV1> resource_, PHLMONITOR monitor_) : m_monitor(monitor_), m_resource(resource_) {\n    if UNLIKELY (!m_resource->resource())\n        return;\n\n    m_resource->setDestroy([](CZxdgOutputV1* pMgr) { PROTO::xdgOutput->onOutputResourceDestroy(pMgr->resource()); });\n    m_resource->setOnDestroy([](CZxdgOutputV1* pMgr) { PROTO::xdgOutput->onOutputResourceDestroy(pMgr->resource()); });\n}\n\nvoid CXDGOutput::sendDetails() {\n    static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>(\"xwayland:force_zero_scaling\");\n\n    if UNLIKELY (!m_monitor || !m_outputProto || m_outputProto->isDefunct())\n        return;\n\n    const auto POS = m_isXWayland ? m_monitor->m_xwaylandPosition : m_monitor->m_position;\n    m_resource->sendLogicalPosition(POS.x, POS.y);\n\n    if (*PXWLFORCESCALEZERO && m_isXWayland)\n        m_resource->sendLogicalSize(m_monitor->m_transformedSize.x, m_monitor->m_transformedSize.y);\n    else\n        m_resource->sendLogicalSize(m_monitor->m_size.x, m_monitor->m_size.y);\n\n    if (m_resource->version() < OUTPUT_DONE_DEPRECATED_SINCE_VERSION)\n        m_resource->sendDone();\n}\n"
  },
  {
    "path": "src/protocols/XDGOutput.hpp",
    "content": "#pragma once\n\n#include \"xdg-output-unstable-v1.hpp\"\n#include \"WaylandProtocol.hpp\"\n#include <optional>\n\nclass CMonitor;\nclass CXDGOutputProtocol;\nclass CWLOutputProtocol;\n\nclass CXDGOutput {\n  public:\n    CXDGOutput(SP<CZxdgOutputV1> resource, PHLMONITOR monitor_);\n\n    void sendDetails();\n\n  private:\n    PHLMONITORREF         m_monitor;\n    SP<CZxdgOutputV1>     m_resource;\n    WP<CWLOutputProtocol> m_outputProto;\n\n    wl_client*            m_client     = nullptr;\n    bool                  m_isXWayland = false;\n\n    friend class CXDGOutputProtocol;\n};\n\nclass CXDGOutputProtocol : public IWaylandProtocol {\n  public:\n    CXDGOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n    void         updateAllOutputs();\n\n  private:\n    void onManagerResourceDestroy(wl_resource* res);\n    void onOutputResourceDestroy(wl_resource* res);\n    void onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* outputResource);\n\n    //\n    std::vector<UP<CZxdgOutputManagerV1>> m_managerResources;\n    std::vector<UP<CXDGOutput>>           m_xdgOutputs;\n\n    friend class CXDGOutput;\n};\n\nnamespace PROTO {\n    inline UP<CXDGOutputProtocol> xdgOutput;\n};\n"
  },
  {
    "path": "src/protocols/XDGShell.cpp",
    "content": "#include \"XDGShell.hpp\"\n#include \"XDGDialog.hpp\"\n#include <algorithm>\n#include \"../Compositor.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../managers/ANRManager.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"core/Seat.hpp\"\n#include \"core/Compositor.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"protocols/core/Output.hpp\"\n#include <cstddef>\n#include <cstring>\n#include <ranges>\n\nvoid SXDGPositionerState::setAnchor(xdgPositionerAnchor edges) {\n    anchor.setTop(edges == XDG_POSITIONER_ANCHOR_TOP || edges == XDG_POSITIONER_ANCHOR_TOP_LEFT || edges == XDG_POSITIONER_ANCHOR_TOP_RIGHT);\n    anchor.setLeft(edges == XDG_POSITIONER_ANCHOR_LEFT || edges == XDG_POSITIONER_ANCHOR_TOP_LEFT || edges == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);\n    anchor.setBottom(edges == XDG_POSITIONER_ANCHOR_BOTTOM || edges == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT || edges == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT);\n    anchor.setRight(edges == XDG_POSITIONER_ANCHOR_RIGHT || edges == XDG_POSITIONER_ANCHOR_TOP_RIGHT || edges == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT);\n}\n\nvoid SXDGPositionerState::setGravity(xdgPositionerGravity edges) {\n    gravity.setTop(edges == XDG_POSITIONER_GRAVITY_TOP || edges == XDG_POSITIONER_GRAVITY_TOP_LEFT || edges == XDG_POSITIONER_GRAVITY_TOP_RIGHT);\n    gravity.setLeft(edges == XDG_POSITIONER_GRAVITY_LEFT || edges == XDG_POSITIONER_GRAVITY_TOP_LEFT || edges == XDG_POSITIONER_GRAVITY_BOTTOM_LEFT);\n    gravity.setBottom(edges == XDG_POSITIONER_GRAVITY_BOTTOM || edges == XDG_POSITIONER_GRAVITY_BOTTOM_LEFT || edges == XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);\n    gravity.setRight(edges == XDG_POSITIONER_GRAVITY_RIGHT || edges == XDG_POSITIONER_GRAVITY_TOP_RIGHT || edges == XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);\n}\n\nCXDGPopupResource::CXDGPopupResource(SP<CXdgPopup> resource_, SP<CXDGSurfaceResource> owner_, SP<CXDGSurfaceResource> surface_, SP<CXDGPositionerResource> positioner) :\n    m_surface(surface_), m_parent(owner_), m_resource(resource_), m_positionerRules(positioner) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CXdgPopup* r) {\n        if (m_surface && m_surface->m_mapped)\n            m_surface->m_events.unmap.emit();\n        PROTO::xdgShell->onPopupDestroy(m_self);\n        m_events.destroy.emit();\n        PROTO::xdgShell->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CXdgPopup* r) {\n        if (m_surface && m_surface->m_mapped)\n            m_surface->m_events.unmap.emit();\n        PROTO::xdgShell->onPopupDestroy(m_self);\n        m_events.destroy.emit();\n        PROTO::xdgShell->destroyResource(this);\n    });\n\n    m_resource->setReposition([this](CXdgPopup* r, wl_resource* positionerRes, uint32_t token) {\n        LOGM(Log::DEBUG, \"Popup {:x} asks for reposition\", (uintptr_t)this);\n        m_lastRepositionToken = token;\n        auto pos              = CXDGPositionerResource::fromResource(positionerRes);\n        if (!pos)\n            return;\n        m_positionerRules = CXDGPositionerRules{pos};\n        m_events.reposition.emit();\n    });\n\n    m_resource->setGrab([this](CXdgPopup* r, wl_resource* seat, uint32_t serial) {\n        LOGM(Log::DEBUG, \"xdg_popup {:x} requests grab\", (uintptr_t)this);\n        PROTO::xdgShell->addOrStartGrab(m_self.lock());\n    });\n\n    if (m_parent)\n        m_taken = true;\n}\n\nCXDGPopupResource::~CXDGPopupResource() {\n    PROTO::xdgShell->onPopupDestroy(m_self);\n    m_events.destroy.emit();\n}\n\nvoid CXDGPopupResource::applyPositioning(const CBox& box, const Vector2D& t1coord) {\n    CBox constraint = box.copy().translate(m_surface->m_pending.geometry.pos());\n\n    m_geometry = m_positionerRules.getPosition(constraint, accumulateParentOffset() + t1coord);\n\n    LOGM(Log::DEBUG, \"Popup {:x} gets unconstrained to {} {}\", (uintptr_t)this, m_geometry.pos(), m_geometry.size());\n\n    configure(m_geometry);\n\n    if UNLIKELY (m_lastRepositionToken)\n        repositioned();\n}\n\nVector2D CXDGPopupResource::accumulateParentOffset() {\n    SP<CXDGSurfaceResource> current = m_parent.lock();\n    Vector2D                off;\n    while (current) {\n        off += current->m_current.geometry.pos();\n        if (current->m_popup) {\n            off += current->m_popup->m_geometry.pos();\n            current = current->m_popup->m_parent.lock();\n        } else\n            break;\n    }\n    return off;\n}\n\nSP<CXDGPopupResource> CXDGPopupResource::fromResource(wl_resource* res) {\n    auto data = sc<CXDGPopupResource*>(sc<CXdgPopup*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CXDGPopupResource::good() {\n    return m_resource->resource();\n}\n\nvoid CXDGPopupResource::configure(const CBox& box) {\n    m_resource->sendConfigure(box.x, box.y, box.w, box.h);\n    if (m_surface)\n        m_surface->scheduleConfigure();\n}\n\nvoid CXDGPopupResource::done() {\n    m_events.dismissed.emit();\n    m_resource->sendPopupDone();\n}\n\nvoid CXDGPopupResource::repositioned() {\n    if LIKELY (!m_lastRepositionToken)\n        return;\n\n    LOGM(Log::DEBUG, \"repositioned: sending reposition token {}\", m_lastRepositionToken);\n\n    m_resource->sendRepositioned(m_lastRepositionToken);\n    m_lastRepositionToken = 0;\n}\n\nCXDGToplevelResource::CXDGToplevelResource(SP<CXdgToplevel> resource_, SP<CXDGSurfaceResource> owner_) : m_owner(owner_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CXdgToplevel* r) {\n        m_events.destroy.emit();\n        PROTO::xdgShell->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CXdgToplevel* r) {\n        m_events.destroy.emit();\n        PROTO::xdgShell->destroyResource(this);\n    });\n\n    if (m_resource->version() >= 5) {\n        wl_array arr;\n        wl_array_init(&arr);\n        auto p = sc<uint32_t*>(wl_array_add(&arr, sizeof(uint32_t)));\n        *p     = XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN;\n        p      = sc<uint32_t*>(wl_array_add(&arr, sizeof(uint32_t)));\n        *p     = XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE;\n        m_resource->sendWmCapabilities(&arr);\n        wl_array_release(&arr);\n    }\n\n    if (m_resource->version() >= 2) {\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_LEFT);\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_RIGHT);\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_TOP);\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_BOTTOM);\n    }\n\n    m_resource->setSetTitle([this](CXdgToplevel* r, const char* t) {\n        m_state.title = t;\n        m_events.metadataChanged.emit();\n    });\n\n    m_resource->setSetAppId([this](CXdgToplevel* r, const char* id) {\n        m_state.appid = id;\n        m_events.metadataChanged.emit();\n    });\n\n    m_resource->setSetMaxSize([this](CXdgToplevel* r, int32_t x, int32_t y) {\n        m_pending.maxSize = {x, y};\n        m_events.sizeLimitsChanged.emit();\n    });\n\n    m_resource->setSetMinSize([this](CXdgToplevel* r, int32_t x, int32_t y) {\n        m_pending.minSize = {x, y};\n        m_events.sizeLimitsChanged.emit();\n    });\n\n    m_resource->setSetMaximized([this](CXdgToplevel* r) {\n        m_state.requestsMaximize = true;\n        m_events.stateChanged.emit();\n        m_state.requestsMaximize.reset();\n    });\n\n    m_resource->setUnsetMaximized([this](CXdgToplevel* r) {\n        m_state.requestsMaximize = false;\n        m_events.stateChanged.emit();\n        m_state.requestsMaximize.reset();\n    });\n\n    m_resource->setSetFullscreen([this](CXdgToplevel* r, wl_resource* output) {\n        if (output)\n            if (const auto PM = CWLOutputResource::fromResource(output)->m_monitor; PM)\n                m_state.requestsFullscreenMonitor = PM->m_id;\n\n        m_state.requestsFullscreen = true;\n        m_events.stateChanged.emit();\n        m_state.requestsFullscreen.reset();\n        m_state.requestsFullscreenMonitor.reset();\n    });\n\n    m_resource->setUnsetFullscreen([this](CXdgToplevel* r) {\n        m_state.requestsFullscreen = false;\n        m_events.stateChanged.emit();\n        m_state.requestsFullscreen.reset();\n    });\n\n    m_resource->setSetMinimized([this](CXdgToplevel* r) {\n        m_state.requestsMinimize = true;\n        m_events.stateChanged.emit();\n        m_state.requestsMinimize.reset();\n    });\n\n    m_resource->setSetParent([this](CXdgToplevel* r, wl_resource* parentR) {\n        auto oldParent = m_parent;\n\n        if (m_parent)\n            std::erase(m_parent->m_children, m_self);\n\n        auto newp = parentR ? CXDGToplevelResource::fromResource(parentR) : nullptr;\n\n        if (newp) {\n            // check for protocol constraints\n            if (newp == m_self) {\n                r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, \"Parent cannot be self\");\n                return;\n            }\n\n            static std::function<bool(WP<CXDGToplevelResource>, WP<CXDGToplevelResource>)> exploreChildren = [](WP<CXDGToplevelResource> tl,\n                                                                                                                WP<CXDGToplevelResource> target) -> bool {\n                bool any = false;\n                for (const auto& c : tl->m_children) {\n                    if (c == target)\n                        return true;\n\n                    any = any || exploreChildren(c, target);\n\n                    if (any)\n                        break;\n                }\n                return any;\n            };\n\n            if (exploreChildren(m_self, newp)) {\n                r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, \"Parent cannot be a descendant\");\n                return;\n            }\n        }\n\n        m_parent = newp;\n\n        if (m_parent) {\n            m_parent->m_children.emplace_back(m_self);\n\n            if (m_parent->m_window && m_parent->m_window->m_pinned)\n                m_self->m_window->m_pinned = true;\n        }\n\n        LOGM(Log::DEBUG, \"Toplevel {:x} sets parent to {:x}{}\", (uintptr_t)this, (uintptr_t)newp.get(), (oldParent ? std::format(\" (was {:x})\", (uintptr_t)oldParent.get()) : \"\"));\n    });\n}\n\nCXDGToplevelResource::~CXDGToplevelResource() {\n    m_events.destroy.emit();\n    if (m_parent)\n        std::erase_if(m_parent->m_children, [this](const auto& other) { return !other || other.get() == this; });\n}\n\nSP<CXDGToplevelResource> CXDGToplevelResource::fromResource(wl_resource* res) {\n    auto data = sc<CXDGToplevelResource*>(sc<CXdgToplevel*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CXDGToplevelResource::good() {\n    return m_resource->resource();\n}\n\nbool CXDGToplevelResource::anyChildModal() {\n    return std::ranges::any_of(m_children, [](const auto& child) { return child && child->m_dialog && child->m_dialog->modal; });\n}\n\nuint32_t CXDGToplevelResource::setSize(const Vector2D& size) {\n    m_pendingApply.size = size;\n    applyState();\n    return m_owner->scheduleConfigure();\n}\n\nuint32_t CXDGToplevelResource::setMaximized(bool maximized) {\n    bool set = std::ranges::find(m_pendingApply.states, XDG_TOPLEVEL_STATE_MAXIMIZED) != m_pendingApply.states.end();\n\n    if (maximized == set)\n        return m_owner->m_scheduledSerial;\n\n    if (maximized && !set)\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_MAXIMIZED);\n    else if (!maximized && set)\n        std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_MAXIMIZED);\n    applyState();\n    return m_owner->scheduleConfigure();\n}\n\nuint32_t CXDGToplevelResource::setFullscreen(bool fullscreen) {\n    bool set = std::ranges::find(m_pendingApply.states, XDG_TOPLEVEL_STATE_FULLSCREEN) != m_pendingApply.states.end();\n\n    if (fullscreen == set)\n        return m_owner->m_scheduledSerial;\n\n    if (fullscreen && !set)\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_FULLSCREEN);\n    else if (!fullscreen && set)\n        std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_FULLSCREEN);\n    applyState();\n    return m_owner->scheduleConfigure();\n}\n\nuint32_t CXDGToplevelResource::setActive(bool active) {\n    bool set = std::ranges::find(m_pendingApply.states, XDG_TOPLEVEL_STATE_ACTIVATED) != m_pendingApply.states.end();\n\n    if (active == set)\n        return m_owner->m_scheduledSerial;\n\n    if (active && !set)\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_ACTIVATED);\n    else if (!active && set)\n        std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_ACTIVATED);\n    applyState();\n    return m_owner->scheduleConfigure();\n}\n\nuint32_t CXDGToplevelResource::setSuspeneded(bool sus) {\n    if (m_resource->version() < 6)\n        return m_owner->scheduleConfigure(); // SUSPENDED is since 6\n\n    bool set = std::ranges::find(m_pendingApply.states, XDG_TOPLEVEL_STATE_SUSPENDED) != m_pendingApply.states.end();\n\n    if (sus == set)\n        return m_owner->m_scheduledSerial;\n\n    if (sus && !set)\n        m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_SUSPENDED);\n    else if (!sus && set)\n        std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_SUSPENDED);\n    applyState();\n    return m_owner->scheduleConfigure();\n}\n\nvoid CXDGToplevelResource::applyState() {\n    wl_array arr;\n    wl_array_init(&arr);\n\n    if (!m_pendingApply.states.empty()) {\n        wl_array_add(&arr, m_pendingApply.states.size() * sizeof(int));\n        memcpy(arr.data, m_pendingApply.states.data(), m_pendingApply.states.size() * sizeof(int));\n    }\n\n    m_resource->sendConfigure(m_pendingApply.size.x, m_pendingApply.size.y, &arr);\n\n    wl_array_release(&arr);\n}\n\nvoid CXDGToplevelResource::close() {\n    m_resource->sendClose();\n}\n\nVector2D CXDGToplevelResource::layoutMinSize() {\n    Vector2D minSize;\n    if (m_current.minSize.x > 1)\n        minSize.x = m_owner ? m_current.minSize.x + m_owner->m_current.geometry.pos().x : m_current.minSize.x;\n    if (m_current.minSize.y > 1)\n        minSize.y = m_owner ? m_current.minSize.y + m_owner->m_current.geometry.pos().y : m_current.minSize.y;\n    return minSize;\n}\n\nVector2D CXDGToplevelResource::layoutMaxSize() {\n    Vector2D maxSize;\n    if (m_current.maxSize.x > 1)\n        maxSize.x = m_owner ? m_current.maxSize.x + m_owner->m_current.geometry.pos().x : m_current.maxSize.x;\n    if (m_current.maxSize.y > 1)\n        maxSize.y = m_owner ? m_current.maxSize.y + m_owner->m_current.geometry.pos().y : m_current.maxSize.y;\n    return maxSize;\n}\n\nCXDGSurfaceResource::CXDGSurfaceResource(SP<CXdgSurface> resource_, SP<CXDGWMBase> owner_, SP<CWLSurfaceResource> surface_) :\n    m_owner(owner_), m_surface(surface_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CXdgSurface* r) {\n        if (m_mapped)\n            m_events.unmap.emit();\n        m_events.destroy.emit();\n        PROTO::xdgShell->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CXdgSurface* r) {\n        if (m_mapped)\n            m_events.unmap.emit();\n        m_events.destroy.emit();\n        PROTO::xdgShell->destroyResource(this);\n    });\n\n    m_listeners.surfaceDestroy = m_surface->m_events.destroy.listen([this] {\n        LOGM(Log::WARN, \"wl_surface destroyed before its xdg_surface role object\");\n        m_listeners.surfaceDestroy.reset();\n        m_listeners.surfaceCommit.reset();\n\n        if (m_mapped)\n            m_events.unmap.emit();\n\n        m_mapped = false;\n        m_surface.reset();\n        m_events.destroy.emit();\n    });\n\n    m_listeners.surfaceCommit = m_surface->m_events.commit.listen([this] {\n        m_current = m_pending;\n        if (m_toplevel)\n            m_toplevel->m_current = m_toplevel->m_pending;\n\n        if UNLIKELY (m_initialCommit && m_surface->m_pending.buffer) {\n            m_resource->error(-1, \"Buffer attached before initial commit\");\n            return;\n        }\n\n        if (m_surface->m_current.texture && !m_mapped) {\n            // this forces apps to not draw CSD.\n            if (m_toplevel)\n                m_toplevel->setMaximized(true);\n\n            m_mapped = true;\n            m_surface->map();\n            m_events.map.emit();\n            return;\n        }\n\n        if (!m_surface->m_current.texture && m_mapped) {\n            m_mapped = false;\n            m_events.unmap.emit();\n            m_surface->unmap();\n            return;\n        }\n\n        m_events.commit.emit();\n        m_initialCommit = false;\n    });\n\n    m_resource->setGetToplevel([this](CXdgSurface* r, uint32_t id) {\n        const auto RESOURCE = PROTO::xdgShell->m_toplevels.emplace_back(makeShared<CXDGToplevelResource>(makeShared<CXdgToplevel>(r->client(), r->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::xdgShell->m_toplevels.pop_back();\n            return;\n        }\n\n        m_toplevel         = RESOURCE;\n        m_toplevel->m_self = RESOURCE;\n\n        LOGM(Log::DEBUG, \"xdg_surface {:x} gets a toplevel {:x}\", (uintptr_t)m_owner.get(), (uintptr_t)RESOURCE.get());\n\n        PHLWINDOW createdWindow = g_pCompositor->m_windows.emplace_back(Desktop::View::CWindow::create(m_self.lock()));\n\n        if (RESOURCE->m_parent && RESOURCE->m_parent->m_window->m_pinned)\n            createdWindow->m_pinned = true;\n\n        for (auto const& p : m_popups) {\n            if (!p)\n                continue;\n            m_events.newPopup.emit(p.lock());\n        }\n    });\n\n    m_resource->setGetPopup([this](CXdgSurface* r, uint32_t id, wl_resource* parentXDG, wl_resource* positionerRes) {\n        auto       parent     = parentXDG ? CXDGSurfaceResource::fromResource(parentXDG) : nullptr;\n        auto       positioner = CXDGPositionerResource::fromResource(positionerRes);\n        const auto RESOURCE =\n            PROTO::xdgShell->m_popups.emplace_back(makeShared<CXDGPopupResource>(makeShared<CXdgPopup>(r->client(), r->version(), id), parent, m_self.lock(), positioner));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::xdgShell->m_popups.pop_back();\n            return;\n        }\n\n        m_popup          = RESOURCE;\n        RESOURCE->m_self = RESOURCE;\n\n        LOGM(Log::DEBUG, \"xdg_surface {:x} gets a popup {:x} owner {:x}\", (uintptr_t)m_self.get(), (uintptr_t)RESOURCE.get(), (uintptr_t)parent.get());\n\n        if (!parent)\n            return;\n\n        parent->m_popups.emplace_back(RESOURCE);\n        if (parent->m_mapped)\n            parent->m_events.newPopup.emit(RESOURCE);\n    });\n\n    m_resource->setAckConfigure([this](CXdgSurface* r, uint32_t serial) {\n        if (serial < m_lastConfigureSerial)\n            return;\n        m_lastConfigureSerial = serial;\n        m_events.ack.emit(serial);\n    });\n\n    m_resource->setSetWindowGeometry([this](CXdgSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) {\n        LOGM(Log::DEBUG, \"xdg_surface {:x} requests geometry {}x{} {}x{}\", (uintptr_t)this, x, y, w, h);\n        m_pending.geometry = {x, y, w, h};\n    });\n}\n\nCXDGSurfaceResource::~CXDGSurfaceResource() {\n    m_events.destroy.emit();\n    if (m_configureSource)\n        wl_event_source_remove(m_configureSource);\n    if (m_surface)\n        m_surface->resetRole();\n}\n\nbool CXDGSurfaceResource::good() {\n    return m_resource->resource();\n}\n\nSP<CXDGSurfaceResource> CXDGSurfaceResource::fromResource(wl_resource* res) {\n    auto data = sc<CXDGSurfaceResource*>(sc<CXdgSurface*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nstatic void onConfigure(void* data) {\n    sc<CXDGSurfaceResource*>(data)->configure();\n}\n\nuint32_t CXDGSurfaceResource::scheduleConfigure() {\n    if (m_configureSource)\n        return m_scheduledSerial;\n\n    m_configureSource = wl_event_loop_add_idle(g_pCompositor->m_wlEventLoop, onConfigure, this);\n    m_scheduledSerial = wl_display_next_serial(g_pCompositor->m_wlDisplay);\n\n    return m_scheduledSerial;\n}\n\nvoid CXDGSurfaceResource::configure() {\n    m_configureSource = nullptr;\n    m_resource->sendConfigure(m_scheduledSerial);\n}\n\nCXDGPositionerResource::CXDGPositionerResource(SP<CXdgPositioner> resource_, SP<CXDGWMBase> owner_) : m_owner(owner_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CXdgPositioner* r) { PROTO::xdgShell->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXdgPositioner* r) { PROTO::xdgShell->destroyResource(this); });\n\n    m_resource->setSetSize([this](CXdgPositioner* r, int32_t x, int32_t y) {\n        if UNLIKELY (x <= 0 || y <= 0) {\n            r->error(XDG_POSITIONER_ERROR_INVALID_INPUT, \"Invalid size\");\n            return;\n        }\n\n        m_state.requestedSize = {x, y};\n    });\n\n    m_resource->setSetAnchorRect([this](CXdgPositioner* r, int32_t x, int32_t y, int32_t w, int32_t h) {\n        if UNLIKELY (w <= 0 || h <= 0) {\n            r->error(XDG_POSITIONER_ERROR_INVALID_INPUT, \"Invalid box\");\n            return;\n        }\n\n        m_state.anchorRect = {x, y, w, h};\n    });\n\n    m_resource->setSetOffset([this](CXdgPositioner* r, int32_t x, int32_t y) { m_state.offset = {x, y}; });\n\n    m_resource->setSetAnchor([this](CXdgPositioner* r, xdgPositionerAnchor a) { m_state.setAnchor(a); });\n\n    m_resource->setSetGravity([this](CXdgPositioner* r, xdgPositionerGravity g) { m_state.setGravity(g); });\n\n    m_resource->setSetConstraintAdjustment([this](CXdgPositioner* r, xdgPositionerConstraintAdjustment a) { m_state.constraintAdjustment = sc<uint32_t>(a); });\n\n    // TODO: support this shit better. The current impl _works_, but is lacking and could be wrong in a few cases.\n    // doesn't matter _that_ much for now, though.\n}\n\nSP<CXDGPositionerResource> CXDGPositionerResource::fromResource(wl_resource* res) {\n    auto data = sc<CXDGPositionerResource*>(sc<CXdgPositioner*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CXDGPositionerResource::good() {\n    return m_resource->resource();\n}\n\nCXDGPositionerRules::CXDGPositionerRules(SP<CXDGPositionerResource> positioner) : m_state(positioner->m_state) {\n    ;\n}\n\nCBox CXDGPositionerRules::getPosition(CBox constraint, const Vector2D& parentCoord) {\n    Log::logger->log(Log::DEBUG, \"GetPosition with constraint {} {} and parent {}\", constraint.pos(), constraint.size(), parentCoord);\n\n    // padding\n    constraint.expand(-4);\n\n    auto anchorRect = m_state.anchorRect.copy().translate(parentCoord);\n\n    auto width   = m_state.requestedSize.x;\n    auto height  = m_state.requestedSize.y;\n    auto gravity = m_state.gravity;\n\n    auto anchorX = m_state.anchor.left() ? anchorRect.x : m_state.anchor.right() ? anchorRect.extent().x : anchorRect.middle().x;\n    auto anchorY = m_state.anchor.top() ? anchorRect.y : m_state.anchor.bottom() ? anchorRect.extent().y : anchorRect.middle().y;\n\n    auto calcEffectiveX = [&](CEdges anchorGravity, double anchorX) { return anchorGravity.left() ? anchorX - width : anchorGravity.right() ? anchorX : anchorX - width / 2; };\n    auto calcEffectiveY = [&](CEdges anchorGravity, double anchorY) { return anchorGravity.top() ? anchorY - height : anchorGravity.bottom() ? anchorY : anchorY - height / 2; };\n\n    auto calcRemainingWidth = [&](double effectiveX) {\n        auto width = m_state.requestedSize.x;\n        if (effectiveX < constraint.x) {\n            auto diff  = constraint.x - effectiveX;\n            effectiveX = constraint.x;\n            width -= diff;\n        }\n\n        auto effectiveX2 = effectiveX + width;\n        if (effectiveX2 > constraint.extent().x)\n            width -= effectiveX2 - constraint.extent().x;\n\n        return std::make_pair(effectiveX, width);\n    };\n\n    auto calcRemainingHeight = [&](double effectiveY) {\n        auto height = m_state.requestedSize.y;\n        if (effectiveY < constraint.y) {\n            auto diff  = constraint.y - effectiveY;\n            effectiveY = constraint.y;\n            height -= diff;\n        }\n\n        auto effectiveY2 = effectiveY + height;\n        if (effectiveY2 > constraint.extent().y)\n            height -= effectiveY2 - constraint.extent().y;\n\n        return std::make_pair(effectiveY, height);\n    };\n\n    auto effectiveX = calcEffectiveX(gravity, anchorX);\n    auto effectiveY = calcEffectiveY(gravity, anchorY);\n\n    // Note: the usage of offset is a guess which maintains compatibility with other compositors that were tested.\n    // It considers the offset when deciding whether or not to flip but does not actually flip the offset, instead\n    // applying it after the flip step.\n\n    if (m_state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X) {\n        auto flip = (gravity.left() && effectiveX + m_state.offset.x < constraint.x) || (gravity.right() && effectiveX + m_state.offset.x + width > constraint.extent().x);\n\n        if (flip) {\n            auto newGravity    = gravity ^ (CEdges::LEFT | CEdges::RIGHT);\n            auto newAnchorX    = m_state.anchor.left() ? anchorRect.extent().x : m_state.anchor.right() ? anchorRect.x : anchorX;\n            auto newEffectiveX = calcEffectiveX(newGravity, newAnchorX);\n\n            if (calcRemainingWidth(newEffectiveX).second > calcRemainingWidth(effectiveX).second) {\n                gravity    = newGravity;\n                anchorX    = newAnchorX;\n                effectiveX = newEffectiveX;\n            }\n        }\n    }\n\n    if (m_state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y) {\n        auto flip =\n            (m_state.gravity.top() && effectiveY + m_state.offset.y < constraint.y) || (m_state.gravity.bottom() && effectiveY + m_state.offset.y + height > constraint.extent().y);\n\n        if (flip) {\n            auto newGravity    = gravity ^ (CEdges::TOP | CEdges::BOTTOM);\n            auto newAnchorY    = m_state.anchor.top() ? anchorRect.extent().y : m_state.anchor.bottom() ? anchorRect.y : anchorY;\n            auto newEffectiveY = calcEffectiveY(newGravity, newAnchorY);\n\n            if (calcRemainingHeight(newEffectiveY).second > calcRemainingHeight(effectiveY).second) {\n                gravity    = newGravity;\n                anchorY    = newAnchorY;\n                effectiveY = newEffectiveY;\n            }\n        }\n    }\n\n    effectiveX += m_state.offset.x;\n    effectiveY += m_state.offset.y;\n\n    // Slide order is important for the case where the window is too large to fit on screen.\n\n    if (m_state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X) {\n        if (effectiveX + width > constraint.extent().x)\n            effectiveX = constraint.extent().x - width;\n\n        if (effectiveX < constraint.x)\n            effectiveX = constraint.x;\n    }\n\n    if (m_state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) {\n        if (effectiveY + height > constraint.extent().y)\n            effectiveY = constraint.extent().y - height;\n\n        if (effectiveY < constraint.y)\n            effectiveY = constraint.y;\n    }\n\n    if (m_state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X) {\n        auto [newX, newWidth] = calcRemainingWidth(effectiveX);\n        effectiveX            = newX;\n        width                 = newWidth;\n    }\n\n    if (m_state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y) {\n        auto [newY, newHeight] = calcRemainingHeight(effectiveY);\n        effectiveY             = newY;\n        height                 = newHeight;\n    }\n\n    return {effectiveX - parentCoord.x, effectiveY - parentCoord.y, width, height};\n}\n\nCXDGWMBase::CXDGWMBase(SP<CXdgWmBase> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXdgWmBase* r) { PROTO::xdgShell->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXdgWmBase* r) { PROTO::xdgShell->destroyResource(this); });\n\n    m_client = m_resource->client();\n\n    m_resource->setCreatePositioner([this](CXdgWmBase* r, uint32_t id) {\n        const auto RESOURCE =\n            PROTO::xdgShell->m_positioners.emplace_back(makeShared<CXDGPositionerResource>(makeShared<CXdgPositioner>(r->client(), r->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::xdgShell->m_positioners.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        m_positioners.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New xdg_positioner at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n\n    m_resource->setGetXdgSurface([this](CXdgWmBase* r, uint32_t id, wl_resource* surf) {\n        auto SURF = CWLSurfaceResource::fromResource(surf);\n\n        if UNLIKELY (!SURF) {\n            r->error(-1, \"Invalid surface passed\");\n            return;\n        }\n\n        if UNLIKELY (SURF->m_role->role() != SURFACE_ROLE_UNASSIGNED) {\n            r->error(-1, \"Surface already has a different role\");\n            return;\n        }\n\n        const auto RESOURCE =\n            PROTO::xdgShell->m_surfaces.emplace_back(makeShared<CXDGSurfaceResource>(makeShared<CXdgSurface>(r->client(), r->version(), id), m_self.lock(), SURF));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::xdgShell->m_surfaces.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self    = RESOURCE;\n        RESOURCE->m_surface = SURF;\n        SURF->m_role        = makeShared<CXDGSurfaceRole>(RESOURCE);\n\n        m_surfaces.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New xdg_surface at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n\n    m_resource->setPong([this](CXdgWmBase* r, uint32_t serial) {\n        g_pANRManager->onResponse(m_self.lock());\n        m_events.pong.emit();\n    });\n}\n\nbool CXDGWMBase::good() {\n    return m_resource->resource();\n}\n\nwl_client* CXDGWMBase::client() {\n    return m_client;\n}\n\nvoid CXDGWMBase::ping() {\n    m_resource->sendPing(1337);\n}\n\nCXDGShellProtocol::CXDGShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    m_grab             = makeShared<CSeatGrab>();\n    m_grab->m_keyboard = true;\n    m_grab->m_pointer  = true;\n    m_grab->setCallback([this]() {\n        for (auto const& g : m_grabbed) {\n            g->done();\n        }\n        m_grabbed.clear();\n    });\n}\n\nvoid CXDGShellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_wmBases.emplace_back(makeShared<CXDGWMBase>(makeShared<CXdgWmBase>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_wmBases.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self = RESOURCE;\n\n    LOGM(Log::DEBUG, \"New xdg_wm_base at {:x}\", (uintptr_t)RESOURCE.get());\n}\n\nvoid CXDGShellProtocol::destroyResource(CXDGWMBase* resource) {\n    std::erase_if(m_wmBases, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CXDGShellProtocol::destroyResource(CXDGPositionerResource* resource) {\n    std::erase_if(m_positioners, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CXDGShellProtocol::destroyResource(CXDGSurfaceResource* resource) {\n    std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CXDGShellProtocol::destroyResource(CXDGToplevelResource* resource) {\n    std::erase_if(m_toplevels, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CXDGShellProtocol::destroyResource(CXDGPopupResource* resource) {\n    std::erase_if(m_popups, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CXDGShellProtocol::addOrStartGrab(SP<CXDGPopupResource> popup) {\n    if (!m_grabOwner) {\n        m_grabOwner = popup;\n        m_grabbed.clear();\n        m_grab->clear();\n        m_grab->add(popup->m_surface->m_surface.lock());\n        if (popup->m_parent)\n            m_grab->add(popup->m_parent->m_surface.lock());\n        g_pSeatManager->setGrab(m_grab);\n        m_grabbed.emplace_back(popup);\n        return;\n    }\n\n    m_grabbed.emplace_back(popup);\n\n    m_grab->add(popup->m_surface->m_surface.lock());\n\n    if (popup->m_parent)\n        m_grab->add(popup->m_parent->m_surface.lock());\n}\n\nvoid CXDGShellProtocol::onPopupDestroy(WP<CXDGPopupResource> popup) {\n    if (popup == m_grabOwner) {\n        g_pSeatManager->setGrab(nullptr);\n        for (auto const& g : m_grabbed) {\n            g->done();\n        }\n        m_grabbed.clear();\n        return;\n    }\n\n    std::erase(m_grabbed, popup);\n    if (popup->m_surface)\n        m_grab->remove(popup->m_surface->m_surface.lock());\n}\n\nCXDGSurfaceRole::CXDGSurfaceRole(SP<CXDGSurfaceResource> xdg) : m_xdgSurface(xdg) {\n    ;\n}\n"
  },
  {
    "path": "src/protocols/XDGShell.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include <optional>\n#include <hyprutils/math/Edges.hpp>\n#include \"WaylandProtocol.hpp\"\n#include \"xdg-shell.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n#include \"types/SurfaceRole.hpp\"\n\nclass CXDGWMBase;\nclass CXDGPositionerResource;\nclass CXDGSurfaceResource;\nclass CXDGToplevelResource;\nclass CXDGPopupResource;\nclass CSeatGrab;\nclass CWLSurfaceResource;\nclass CXDGDialogV1Resource;\n\nstruct SXDGPositionerState {\n    Vector2D requestedSize;\n    CBox     anchorRect;\n    CEdges   anchor;\n    CEdges   gravity;\n    uint32_t constraintAdjustment = 0;\n    Vector2D offset;\n    bool     reactive = false;\n    Vector2D parentSize;\n\n    void     setAnchor(xdgPositionerAnchor edges);\n    void     setGravity(xdgPositionerGravity edges);\n};\n\nclass CXDGPositionerRules {\n  public:\n    CXDGPositionerRules(SP<CXDGPositionerResource> positioner);\n\n    CBox getPosition(CBox constraint, const Vector2D& parentPos);\n\n  private:\n    SXDGPositionerState m_state;\n};\n\nclass CXDGPopupResource {\n  public:\n    CXDGPopupResource(SP<CXdgPopup> resource_, SP<CXDGSurfaceResource> parent_, SP<CXDGSurfaceResource> surface_, SP<CXDGPositionerResource> positioner_);\n    ~CXDGPopupResource();\n\n    static SP<CXDGPopupResource> fromResource(wl_resource*);\n\n    bool                         good();\n\n    void                         applyPositioning(const CBox& availableBox, const Vector2D& t1coord /* relative to box */);\n\n    WP<CXDGSurfaceResource>      m_surface;\n    WP<CXDGSurfaceResource>      m_parent;\n    WP<CXDGPopupResource>        m_self;\n\n    bool                         m_taken = false;\n\n    CBox                         m_geometry;\n\n    struct {\n        CSignalT<> reposition;\n        CSignalT<> dismissed;\n        CSignalT<> destroy; // only the role\n    } m_events;\n\n    // schedules a configure event\n    void configure(const CBox& box);\n\n    void done();\n    void repositioned();\n\n  private:\n    SP<CXdgPopup>       m_resource;\n\n    uint32_t            m_lastRepositionToken = 0;\n\n    Vector2D            accumulateParentOffset();\n\n    CXDGPositionerRules m_positionerRules;\n};\n\nclass CXDGToplevelResource {\n  public:\n    CXDGToplevelResource(SP<CXdgToplevel> resource_, SP<CXDGSurfaceResource> owner_);\n    ~CXDGToplevelResource();\n\n    static SP<CXDGToplevelResource> fromResource(wl_resource*);\n\n    WP<CXDGSurfaceResource>         m_owner;\n    WP<CXDGToplevelResource>        m_self;\n\n    PHLWINDOWREF                    m_window;\n\n    bool                            good();\n\n    Vector2D                        layoutMinSize();\n    Vector2D                        layoutMaxSize();\n\n    // schedule a configure event\n    uint32_t setSize(const Vector2D& size);\n    uint32_t setMaximized(bool maximized);\n    uint32_t setFullscreen(bool fullscreen);\n    uint32_t setActive(bool active);\n    uint32_t setSuspeneded(bool sus);\n\n    void     close();\n\n    struct {\n        CSignalT<> sizeLimitsChanged;\n        CSignalT<> stateChanged;    // maximized, fs, minimized, etc.\n        CSignalT<> metadataChanged; // title, appid\n        CSignalT<> destroy;         // only the role\n    } m_events;\n\n    struct {\n        std::string title;\n        std::string appid;\n\n        // volatile state: is reset after the stateChanged signal fires\n        std::optional<bool>      requestsMaximize;\n        std::optional<bool>      requestsFullscreen;\n        std::optional<MONITORID> requestsFullscreenMonitor;\n        std::optional<bool>      requestsMinimize;\n    } m_state;\n\n    struct {\n        Vector2D                      size;\n        std::vector<xdgToplevelState> states;\n    } m_pendingApply;\n\n    struct {\n        Vector2D minSize = {1, 1};\n        Vector2D maxSize = {1337420, 694200};\n    } m_pending, m_current;\n\n    WP<CXDGToplevelResource>              m_parent;\n    WP<CXDGDialogV1Resource>              m_dialog;\n\n    std::optional<std::string>            m_toplevelTag;\n    std::optional<std::string>            m_toplevelDescription;\n\n    bool                                  anyChildModal();\n\n    std::vector<WP<CXDGToplevelResource>> m_children;\n\n  private:\n    SP<CXdgToplevel> m_resource;\n    void             applyState();\n};\n\nclass CXDGSurfaceRole : public ISurfaceRole {\n  public:\n    CXDGSurfaceRole(SP<CXDGSurfaceResource> xdg);\n\n    virtual eSurfaceRole role() {\n        return SURFACE_ROLE_XDG_SHELL;\n    }\n\n    WP<CXDGSurfaceResource> m_xdgSurface;\n};\n\nclass CXDGSurfaceResource {\n  public:\n    CXDGSurfaceResource(SP<CXdgSurface> resource_, SP<CXDGWMBase> owner_, SP<CWLSurfaceResource> surface_);\n    ~CXDGSurfaceResource();\n\n    static SP<CXDGSurfaceResource> fromResource(wl_resource*);\n\n    bool                           good();\n\n    WP<CXDGWMBase>                 m_owner;\n    WP<CWLSurfaceResource>         m_surface;\n\n    WP<CXDGToplevelResource>       m_toplevel;\n    WP<CXDGPopupResource>          m_popup;\n\n    WP<CXDGSurfaceResource>        m_self;\n\n    struct {\n        CBox geometry;\n    } m_pending, m_current;\n\n    struct {\n        CSignalT<uint32_t>              ack;\n        CSignalT<>                      commit;\n        CSignalT<>                      map;\n        CSignalT<>                      unmap;\n        CSignalT<>                      destroy;\n        CSignalT<SP<CXDGPopupResource>> newPopup;\n    } m_events;\n\n    bool     m_initialCommit = true;\n    bool     m_mapped        = false;\n\n    uint32_t scheduleConfigure();\n    // do not call directly\n    void configure();\n\n  private:\n    SP<CXdgSurface>  m_resource;\n\n    uint32_t         m_lastConfigureSerial = 0;\n    uint32_t         m_scheduledSerial     = 0;\n\n    wl_event_source* m_configureSource = nullptr;\n\n    //\n    std::vector<WP<CXDGPopupResource>> m_popups;\n\n    struct {\n        CHyprSignalListener surfaceDestroy;\n        CHyprSignalListener surfaceCommit;\n    } m_listeners;\n\n    friend class CXDGPopupResource;\n    friend class CXDGToplevelResource;\n};\n\nclass CXDGPositionerResource {\n  public:\n    CXDGPositionerResource(SP<CXdgPositioner> resource_, SP<CXDGWMBase> owner_);\n\n    static SP<CXDGPositionerResource> fromResource(wl_resource*);\n\n    bool                              good();\n\n    SXDGPositionerState               m_state;\n\n    WP<CXDGWMBase>                    m_owner;\n    WP<CXDGPositionerResource>        m_self;\n\n  private:\n    SP<CXdgPositioner> m_resource;\n};\n\nclass CXDGWMBase {\n  public:\n    CXDGWMBase(SP<CXdgWmBase> resource_);\n\n    bool                                    good();\n    wl_client*                              client();\n    void                                    ping();\n\n    std::vector<WP<CXDGPositionerResource>> m_positioners;\n    std::vector<WP<CXDGSurfaceResource>>    m_surfaces;\n\n    WP<CXDGWMBase>                          m_self;\n\n    struct {\n        CSignalT<> pong;\n    } m_events;\n\n  private:\n    SP<CXdgWmBase> m_resource;\n    wl_client*     m_client = nullptr;\n};\n\nclass CXDGShellProtocol : public IWaylandProtocol {\n  public:\n    CXDGShellProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CXDGWMBase* resource);\n    void destroyResource(CXDGPositionerResource* resource);\n    void destroyResource(CXDGSurfaceResource* resource);\n    void destroyResource(CXDGToplevelResource* resource);\n    void destroyResource(CXDGPopupResource* resource);\n\n    //\n    std::vector<SP<CXDGWMBase>>             m_wmBases;\n    std::vector<SP<CXDGPositionerResource>> m_positioners;\n    std::vector<SP<CXDGSurfaceResource>>    m_surfaces;\n    std::vector<SP<CXDGToplevelResource>>   m_toplevels;\n    std::vector<SP<CXDGPopupResource>>      m_popups;\n\n    // current popup grab\n    WP<CXDGPopupResource>              m_grabOwner;\n    SP<CSeatGrab>                      m_grab;\n    std::vector<WP<CXDGPopupResource>> m_grabbed;\n\n    void                               addOrStartGrab(SP<CXDGPopupResource> popup);\n    void                               onPopupDestroy(WP<CXDGPopupResource> popup);\n\n    friend class CXDGWMBase;\n    friend class CXDGPositionerResource;\n    friend class CXDGSurfaceResource;\n    friend class CXDGToplevelResource;\n    friend class CXDGPopupResource;\n};\n\nnamespace PROTO {\n    inline UP<CXDGShellProtocol> xdgShell;\n};\n"
  },
  {
    "path": "src/protocols/XDGTag.cpp",
    "content": "#include \"XDGTag.hpp\"\n#include \"XDGShell.hpp\"\n#include \"../desktop/view/Window.hpp\"\n\nCXDGToplevelTagManagerResource::CXDGToplevelTagManagerResource(UP<CXdgToplevelTagManagerV1>&& resource) : m_resource(std::move(resource)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXdgToplevelTagManagerV1* r) { PROTO::xdgTag->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXdgToplevelTagManagerV1* r) { PROTO::xdgTag->destroyResource(this); });\n\n    m_resource->setSetToplevelTag([](CXdgToplevelTagManagerV1* r, wl_resource* toplevel, const char* tag) {\n        auto TOPLEVEL = CXDGToplevelResource::fromResource(toplevel);\n\n        if (!TOPLEVEL) {\n            r->error(-1, \"Invalid toplevel handle\");\n            return;\n        }\n\n        TOPLEVEL->m_toplevelTag = tag;\n        if (TOPLEVEL->m_window)\n            TOPLEVEL->m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_XDG_TAG);\n    });\n\n    m_resource->setSetToplevelDescription([](CXdgToplevelTagManagerV1* r, wl_resource* toplevel, const char* description) {\n        auto TOPLEVEL = CXDGToplevelResource::fromResource(toplevel);\n\n        if (!TOPLEVEL) {\n            r->error(-1, \"Invalid toplevel handle\");\n            return;\n        }\n\n        TOPLEVEL->m_toplevelDescription = description;\n    });\n}\n\nbool CXDGToplevelTagManagerResource::good() {\n    return m_resource->resource();\n}\n\nCXDGToplevelTagProtocol::CXDGToplevelTagProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CXDGToplevelTagProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE =\n        WP<CXDGToplevelTagManagerResource>{m_managers.emplace_back(makeUnique<CXDGToplevelTagManagerResource>(makeUnique<CXdgToplevelTagManagerV1>(client, ver, id)))};\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        return;\n    }\n}\n\nvoid CXDGToplevelTagProtocol::destroyResource(CXDGToplevelTagManagerResource* res) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; });\n}\n"
  },
  {
    "path": "src/protocols/XDGTag.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include \"WaylandProtocol.hpp\"\n#include \"xdg-toplevel-tag-v1.hpp\"\n\nclass CXDGToplevelResource;\n\nclass CXDGToplevelTagManagerResource {\n  public:\n    CXDGToplevelTagManagerResource(UP<CXdgToplevelTagManagerV1>&& resource);\n\n    bool good();\n\n  private:\n    UP<CXdgToplevelTagManagerV1> m_resource;\n};\n\nclass CXDGToplevelTagProtocol : public IWaylandProtocol {\n  public:\n    CXDGToplevelTagProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CXDGToplevelTagManagerResource* res);\n\n    //\n    std::vector<UP<CXDGToplevelTagManagerResource>> m_managers;\n\n    friend class CXDGToplevelTagManagerResource;\n};\n\nnamespace PROTO {\n    inline UP<CXDGToplevelTagProtocol> xdgTag;\n};\n"
  },
  {
    "path": "src/protocols/XWaylandShell.cpp",
    "content": "#include \"XWaylandShell.hpp\"\n#include \"core/Compositor.hpp\"\n#include <algorithm>\n\nCXWaylandSurfaceResource::CXWaylandSurfaceResource(SP<CXwaylandSurfaceV1> resource_, SP<CWLSurfaceResource> surface_) : m_surface(surface_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXwaylandSurfaceV1* r) {\n        events.destroy.emit();\n        PROTO::xwaylandShell->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CXwaylandSurfaceV1* r) {\n        events.destroy.emit();\n        PROTO::xwaylandShell->destroyResource(this);\n    });\n\n    m_client = m_resource->client();\n\n    m_resource->setSetSerial([this](CXwaylandSurfaceV1* r, uint32_t lo, uint32_t hi) {\n        m_serial = (sc<uint64_t>(hi) << 32) + lo;\n        PROTO::xwaylandShell->m_events.newSurface.emit(m_self.lock());\n    });\n}\n\nCXWaylandSurfaceResource::~CXWaylandSurfaceResource() {\n    events.destroy.emit();\n}\n\nbool CXWaylandSurfaceResource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CXWaylandSurfaceResource::client() {\n    return m_client;\n}\n\nCXWaylandShellResource::CXWaylandShellResource(SP<CXwaylandShellV1> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CXwaylandShellV1* r) { PROTO::xwaylandShell->destroyResource(this); });\n    m_resource->setOnDestroy([this](CXwaylandShellV1* r) { PROTO::xwaylandShell->destroyResource(this); });\n\n    m_resource->setGetXwaylandSurface([](CXwaylandShellV1* r, uint32_t id, wl_resource* surface) {\n        const auto RESOURCE = PROTO::xwaylandShell->m_surfaces.emplace_back(\n            makeShared<CXWaylandSurfaceResource>(makeShared<CXwaylandSurfaceV1>(r->client(), r->version(), id), CWLSurfaceResource::fromResource(surface)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::xwaylandShell->m_surfaces.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n}\n\nbool CXWaylandShellResource::good() {\n    return m_resource->resource();\n}\n\nCXWaylandShellProtocol::CXWaylandShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CXWaylandShellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CXWaylandShellResource>(makeShared<CXwaylandShellV1>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CXWaylandShellProtocol::destroyResource(CXWaylandShellResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CXWaylandShellProtocol::destroyResource(CXWaylandSurfaceResource* resource) {\n    std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/XWaylandShell.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"WaylandProtocol.hpp\"\n#include \"xwayland-shell-v1.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\nclass CWLSurfaceResource;\n\nclass CXWaylandSurfaceResource {\n  public:\n    CXWaylandSurfaceResource(SP<CXwaylandSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);\n    ~CXWaylandSurfaceResource();\n\n    bool       good();\n    wl_client* client();\n\n    struct {\n        CSignalT<> destroy;\n    } events;\n\n    uint64_t                     m_serial = 0;\n    WP<CWLSurfaceResource>       m_surface;\n\n    WP<CXWaylandSurfaceResource> m_self;\n\n  private:\n    SP<CXwaylandSurfaceV1> m_resource;\n    wl_client*             m_client = nullptr;\n};\n\nclass CXWaylandShellResource {\n  public:\n    CXWaylandShellResource(SP<CXwaylandShellV1> resource_);\n\n    bool good();\n\n  private:\n    SP<CXwaylandShellV1> m_resource;\n};\n\nclass CXWaylandShellProtocol : public IWaylandProtocol {\n  public:\n    CXWaylandShellProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<SP<CXWaylandSurfaceResource>> newSurface; // Fired when it sets a serial, otherwise it's useless\n    } m_events;\n\n  private:\n    void destroyResource(CXWaylandSurfaceResource* resource);\n    void destroyResource(CXWaylandShellResource* resource);\n\n    //\n    std::vector<SP<CXWaylandShellResource>>   m_managers;\n    std::vector<SP<CXWaylandSurfaceResource>> m_surfaces;\n\n    friend class CXWaylandSurfaceResource;\n    friend class CXWaylandShellResource;\n};\n\nnamespace PROTO {\n    inline UP<CXWaylandShellProtocol> xwaylandShell;\n};\n"
  },
  {
    "path": "src/protocols/core/Compositor.cpp",
    "content": "#include \"Compositor.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"Output.hpp\"\n#include \"Seat.hpp\"\n#include \"../types/WLBuffer.hpp\"\n#include <algorithm>\n#include <ranges>\n#include \"Subcompositor.hpp\"\n#include \"../Viewporter.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../PresentationTime.hpp\"\n#include \"../DRMSyncobj.hpp\"\n#include \"../types/DMABuffer.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"config/ConfigValue.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"protocols/types/SurfaceRole.hpp\"\n#include \"render/Texture.hpp\"\n#include <cstring>\n\nusing namespace NColorManagement;\n\nclass CDefaultSurfaceRole : public ISurfaceRole {\n  public:\n    virtual eSurfaceRole role() {\n        return SURFACE_ROLE_UNASSIGNED;\n    }\n};\n\nCWLCallbackResource::CWLCallbackResource(SP<CWlCallback>&& resource_) : m_resource(std::move(resource_)) {\n    ;\n}\n\nbool CWLCallbackResource::good() {\n    return m_resource && m_resource->resource();\n}\n\nvoid CWLCallbackResource::send(const Time::steady_tp& now) {\n    if (!good())\n        return;\n\n    m_resource->sendDone(Time::millis(now));\n    m_resource.reset();\n}\n\nCWLRegionResource::CWLRegionResource(SP<CWlRegion> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CWlRegion* r) { PROTO::compositor->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlRegion* r) { PROTO::compositor->destroyResource(this); });\n\n    m_resource->setAdd([this](CWlRegion* r, int32_t x, int32_t y, int32_t w, int32_t h) { m_region.add(CBox{x, y, w, h}); });\n    m_resource->setSubtract([this](CWlRegion* r, int32_t x, int32_t y, int32_t w, int32_t h) { m_region.subtract(CBox{x, y, w, h}); });\n}\n\nbool CWLRegionResource::good() {\n    return m_resource->resource();\n}\n\nSP<CWLRegionResource> CWLRegionResource::fromResource(wl_resource* res) {\n    auto data = sc<CWLRegionResource*>(sc<CWlRegion*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nCWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_client = m_resource->client();\n\n    m_resource->setData(this);\n\n    m_role = makeShared<CDefaultSurfaceRole>();\n\n    m_resource->setDestroy([this](CWlSurface* r) { destroy(); });\n    m_resource->setOnDestroy([this](CWlSurface* r) { destroy(); });\n\n    m_resource->setAttach([this](CWlSurface* r, wl_resource* buffer, int32_t x, int32_t y) {\n        m_pending.updated.bits.buffer = true;\n        m_pending.updated.bits.offset = true;\n\n        m_pending.offset = {x, y};\n\n        if (m_pending.buffer)\n            m_pending.buffer.drop();\n\n        auto buf = buffer ? CWLBufferResource::fromResource(buffer) : nullptr;\n\n        if (buf && buf->m_buffer) {\n            m_pending.buffer     = CHLBufferReference(buf->m_buffer.lock());\n            m_pending.texture    = buf->m_buffer->m_texture;\n            m_pending.size       = buf->m_buffer->size;\n            m_pending.bufferSize = buf->m_buffer->size;\n        } else {\n            m_pending.buffer = {};\n            m_pending.texture.reset();\n            m_pending.size       = Vector2D{};\n            m_pending.bufferSize = Vector2D{};\n        }\n\n        if (m_pending.bufferSize != m_current.bufferSize) {\n            m_pending.updated.bits.damage = true;\n            m_pending.bufferDamage        = CBox{{}, m_pending.bufferSize};\n        }\n    });\n\n    m_resource->setCommit([this](CWlSurface* r) {\n        if (m_pending.buffer)\n            m_pending.bufferDamage.intersect(CBox{{}, m_pending.bufferSize});\n\n        if (!m_pending.buffer)\n            m_pending.size = {};\n        else if (m_pending.viewport.hasDestination)\n            m_pending.size = m_pending.viewport.destination;\n        else if (m_pending.viewport.hasSource)\n            m_pending.size = m_pending.viewport.source.size();\n        else {\n            Vector2D tfs   = m_pending.transform % 2 == 1 ? Vector2D{m_pending.bufferSize.y, m_pending.bufferSize.x} : m_pending.bufferSize;\n            m_pending.size = tfs / m_pending.scale;\n        }\n\n        m_pending.damage.intersect(CBox{{}, m_pending.size});\n\n        m_events.precommit.emit();\n        if (m_pending.rejected) {\n            m_pending.rejected = false;\n            dropPendingBuffer();\n            return;\n        }\n\n        // null buffer attached\n        if (!m_pending.buffer && m_pending.updated.bits.buffer) {\n            commitState(m_pending);\n\n            // remove any pending states.\n            m_stateQueue.clear();\n            m_pending.reset();\n            return;\n        }\n\n        // save state while we wait for buffer to become ready\n        auto state = m_stateQueue.enqueue(makeUnique<SSurfaceState>(m_pending));\n        m_pending.reset();\n\n        // fifo and fences first\n        m_events.stateCommit.emit(state);\n\n        if (state->buffer && state->buffer->type() == Aquamarine::BUFFER_TYPE_DMABUF && state->buffer->dmabuf().success && !state->updated.bits.acquire) {\n            state->buffer->m_syncFd = dc<CDMABuffer*>(state->buffer.m_buffer.get())->exportSyncFile();\n            if (state->buffer->m_syncFd.isValid())\n                m_stateQueue.lock(state, LOCK_REASON_FENCE);\n        }\n\n        // now for timer.\n        m_events.stateCommit2.emit(state);\n\n        if (state->rejected) {\n            m_stateQueue.dropState(state);\n            return;\n        }\n\n        scheduleState(state);\n    });\n\n    m_resource->setDamage([this](CWlSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) {\n        m_pending.updated.bits.damage = true;\n        m_pending.damage.add(CBox{x, y, w, h});\n    });\n    m_resource->setDamageBuffer([this](CWlSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) {\n        m_pending.updated.bits.damage = true;\n        const auto damageSize         = Vector2D(w, h);\n\n        if (damageSize > m_pending.bufferSize)\n            m_pending.bufferDamage.add(CBox{{x, y}, m_pending.bufferSize});\n        else\n            m_pending.bufferDamage.add(CBox{{x, y}, damageSize});\n    });\n\n    m_resource->setSetBufferScale([this](CWlSurface* r, int32_t scale) {\n        if (scale == m_pending.scale)\n            return;\n\n        m_pending.updated.bits.scale  = true;\n        m_pending.updated.bits.damage = true;\n\n        m_pending.scale        = scale;\n        m_pending.bufferDamage = CBox{{}, m_pending.bufferSize};\n    });\n\n    m_resource->setSetBufferTransform([this](CWlSurface* r, uint32_t tr) {\n        if (tr == m_pending.transform)\n            return;\n\n        m_pending.updated.bits.transform = true;\n        m_pending.updated.bits.damage    = true;\n\n        m_pending.transform    = sc<wl_output_transform>(tr);\n        m_pending.bufferDamage = CBox{{}, m_pending.bufferSize};\n    });\n\n    m_resource->setSetInputRegion([this](CWlSurface* r, wl_resource* region) {\n        m_pending.updated.bits.input = true;\n\n        if (!region) {\n            m_pending.input = CBox{{}, Vector2D{INT32_MAX - 1, INT32_MAX - 1}};\n            return;\n        }\n\n        auto RG         = CWLRegionResource::fromResource(region);\n        m_pending.input = RG->m_region;\n    });\n\n    m_resource->setSetOpaqueRegion([this](CWlSurface* r, wl_resource* region) {\n        m_pending.updated.bits.opaque = true;\n\n        if (!region) {\n            m_pending.opaque = CBox{{}, {}};\n            return;\n        }\n\n        auto RG          = CWLRegionResource::fromResource(region);\n        m_pending.opaque = RG->m_region;\n    });\n\n    m_resource->setFrame([this](CWlSurface* r, uint32_t id) {\n        m_pending.updated.bits.frame = true;\n        m_pending.callbacks.emplace_back(makeShared<CWLCallbackResource>(makeShared<CWlCallback>(m_client, 1, id)));\n    });\n\n    m_resource->setOffset([this](CWlSurface* r, int32_t x, int32_t y) {\n        m_pending.updated.bits.offset = true;\n        m_pending.offset              = {x, y};\n    });\n}\n\nCWLSurfaceResource::~CWLSurfaceResource() {\n    m_events.destroy.emit();\n}\n\nvoid CWLSurfaceResource::destroy() {\n    if (m_mapped) {\n        m_events.unmap.emit();\n        unmap();\n    }\n    m_events.destroy.emit();\n    releaseBuffers(false);\n    PROTO::compositor->destroyResource(this);\n}\n\nvoid CWLSurfaceResource::dropPendingBuffer() {\n    m_pending.buffer = {};\n}\n\nvoid CWLSurfaceResource::dropCurrentBuffer() {\n    m_current.buffer = {};\n}\n\nSP<CWLSurfaceResource> CWLSurfaceResource::fromResource(wl_resource* res) {\n    auto data = sc<CWLSurfaceResource*>(sc<CWlSurface*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CWLSurfaceResource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CWLSurfaceResource::client() {\n    return m_client;\n}\n\nvoid CWLSurfaceResource::enter(PHLMONITOR monitor) {\n    if (std::ranges::find(m_enteredOutputs, monitor) != m_enteredOutputs.end())\n        return;\n\n    if UNLIKELY (!PROTO::outputs.contains(monitor->m_name)) {\n        // can happen on unplug/replug\n        LOGM(Log::ERR, \"enter() called on a non-existent output global\");\n        return;\n    }\n\n    if UNLIKELY (PROTO::outputs.at(monitor->m_name)->isDefunct()) {\n        LOGM(Log::ERR, \"enter() called on a defunct output global\");\n        return;\n    }\n\n    auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client);\n\n    if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) {\n        LOGM(Log::ERR, \"Cannot enter surface {:x} to {}, client hasn't bound the output\", (uintptr_t)this, monitor->m_name);\n        return;\n    }\n\n    m_enteredOutputs.emplace_back(monitor);\n\n    for (const auto& o : outputs) {\n        if (!o->getResource() || !o->getResource()->resource())\n            continue;\n        m_resource->sendEnter(o->getResource().get());\n    }\n    m_events.enter.emit(monitor);\n}\n\nvoid CWLSurfaceResource::leave(PHLMONITOR monitor) {\n    if UNLIKELY (std::ranges::find(m_enteredOutputs, monitor) == m_enteredOutputs.end())\n        return;\n\n    auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client);\n\n    if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) {\n        LOGM(Log::ERR, \"Cannot leave surface {:x} from {}, client hasn't bound the output\", (uintptr_t)this, monitor->m_name);\n        return;\n    }\n\n    std::erase(m_enteredOutputs, monitor);\n\n    for (const auto& o : outputs) {\n        if (!o->getResource() || !o->getResource()->resource())\n            continue;\n        m_resource->sendLeave(o->getResource().get());\n    }\n    m_events.leave.emit(monitor);\n}\n\nvoid CWLSurfaceResource::sendPreferredTransform(wl_output_transform t) {\n    if (m_resource->version() < 6)\n        return;\n    m_resource->sendPreferredBufferTransform(t);\n}\n\nvoid CWLSurfaceResource::sendPreferredScale(int32_t scale) {\n    if (m_resource->version() < 6)\n        return;\n    m_resource->sendPreferredBufferScale(scale);\n}\n\nvoid CWLSurfaceResource::frame(const Time::steady_tp& now) {\n    if (m_current.callbacks.empty())\n        return;\n\n    for (auto const& c : m_current.callbacks) {\n        c->send(now);\n    }\n\n    m_current.callbacks.clear();\n}\n\nvoid CWLSurfaceResource::resetRole() {\n    m_role = makeShared<CDefaultSurfaceRole>();\n}\n\nvoid CWLSurfaceResource::bfHelper(std::vector<SP<CWLSurfaceResource>> const& nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data) {\n    std::vector<SP<CWLSurfaceResource>> nodes2;\n    nodes2.reserve(nodes.size() * 2);\n\n    // first, gather all nodes below\n    for (auto const& n : nodes) {\n        std::erase_if(n->m_subsurfaces, [](const auto& e) { return e.expired(); });\n        // subsurfaces is sorted lowest -> highest\n        for (auto const& c : n->m_subsurfaces) {\n            if (c->m_zIndex >= 0)\n                break;\n            if (c->m_surface.expired())\n                continue;\n            nodes2.emplace_back(c->m_surface.lock());\n        }\n    }\n\n    if (!nodes2.empty())\n        bfHelper(nodes2, fn, data);\n\n    nodes2.clear();\n\n    for (auto const& n : nodes) {\n        Vector2D offset = {};\n        if (n->m_role->role() == SURFACE_ROLE_SUBSURFACE) {\n            auto subsurface = sc<CSubsurfaceRole*>(n->m_role.get())->m_subsurface.lock();\n            offset          = subsurface->posRelativeToParent();\n        }\n\n        fn(n, offset, data);\n    }\n\n    for (auto const& n : nodes) {\n        for (auto const& c : n->m_subsurfaces) {\n            if (c->m_zIndex < 0)\n                continue;\n            if (c->m_surface.expired())\n                continue;\n            nodes2.emplace_back(c->m_surface.lock());\n        }\n    }\n\n    if (!nodes2.empty())\n        bfHelper(nodes2, fn, data);\n}\n\nvoid CWLSurfaceResource::breadthfirst(std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data) {\n    std::vector<SP<CWLSurfaceResource>> surfs;\n    surfs.emplace_back(m_self.lock());\n    bfHelper(surfs, fn, data);\n}\n\nSP<CWLSurfaceResource> CWLSurfaceResource::findFirstPreorderHelper(SP<CWLSurfaceResource> root, std::function<bool(SP<CWLSurfaceResource>)> fn) {\n    if (fn(root))\n        return root;\n    for (auto const& sub : root->m_subsurfaces) {\n        if (sub.expired() || sub->m_surface.expired())\n            continue;\n        const auto found = findFirstPreorderHelper(sub->m_surface.lock(), fn);\n        if (found)\n            return found;\n    }\n    return nullptr;\n}\n\nSP<CWLSurfaceResource> CWLSurfaceResource::findFirstPreorder(std::function<bool(SP<CWLSurfaceResource>)> fn) {\n    return findFirstPreorderHelper(m_self.lock(), fn);\n}\n\nSP<CWLSurfaceResource> CWLSurfaceResource::findWithCM() {\n    return findFirstPreorder([this](SP<CWLSurfaceResource> surf) { return surf->m_colorManagement.valid() && surf->extends() == extends(); });\n}\n\nstd::pair<SP<CWLSurfaceResource>, Vector2D> CWLSurfaceResource::at(const Vector2D& localCoords, bool allowsInput) {\n    std::vector<std::pair<SP<CWLSurfaceResource>, Vector2D>> surfs;\n    breadthfirst([&surfs](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) { surfs.emplace_back(std::make_pair<>(surf, offset)); }, &surfs);\n\n    for (auto const& [surf, pos] : surfs | std::views::reverse) {\n        if (!allowsInput) {\n            const auto BOX = CBox{pos, surf->m_current.size};\n            if (BOX.containsPoint(localCoords))\n                return {surf, localCoords - pos};\n        } else {\n            const auto REGION = surf->m_current.input.copy().intersect(CBox{{}, surf->m_current.size}).translate(pos);\n            if (REGION.containsPoint(localCoords))\n                return {surf, localCoords - pos};\n        }\n    }\n\n    return {nullptr, {}};\n}\n\nuint32_t CWLSurfaceResource::id() {\n    return wl_resource_get_id(m_resource->resource());\n}\n\nvoid CWLSurfaceResource::map() {\n    if UNLIKELY (m_mapped)\n        return;\n\n    m_mapped = true;\n\n    frame(Time::steadyNow());\n\n    m_current.bufferDamage = CBox{{}, m_current.bufferSize};\n    m_pending.bufferDamage = CBox{{}, m_pending.bufferSize};\n}\n\nvoid CWLSurfaceResource::unmap() {\n    if UNLIKELY (!m_mapped)\n        return;\n\n    m_mapped = false;\n\n    // release the buffers.\n    // this is necessary for XWayland to function correctly,\n    // as it does not unmap via the traditional commit(null buffer) method, but via the X11 protocol.\n    releaseBuffers();\n}\n\nvoid CWLSurfaceResource::releaseBuffers(bool onlyCurrent) {\n    if (!onlyCurrent)\n        dropPendingBuffer();\n    dropCurrentBuffer();\n}\n\nvoid CWLSurfaceResource::error(int code, const std::string& str) {\n    m_resource->error(code, str);\n}\n\nSP<CWlSurface> CWLSurfaceResource::getResource() {\n    return m_resource;\n}\n\nCBox CWLSurfaceResource::extends() {\n    CRegion full = CBox{{}, m_current.size};\n    breadthfirst(\n        [](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* d) {\n            if (surf->m_role->role() != SURFACE_ROLE_SUBSURFACE)\n                return;\n\n            sc<CRegion*>(d)->add(CBox{offset, surf->m_current.size});\n        },\n        &full);\n    return full.getExtents();\n}\n\nvoid CWLSurfaceResource::scheduleState(WP<SSurfaceState> state) {\n    auto whenReadable = [this, surf = m_self](auto state, auto reason) {\n        if (!surf || !state)\n            return;\n\n        m_stateQueue.unlock(state, reason);\n    };\n\n    if (state->updated.bits.acquire) {\n        // wait on acquire point for this surface, from explicit sync protocol\n        if (!state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); })) {\n            Log::logger->log(Log::ERR, \"Failed to addWaiter in CWLSurfaceResource::scheduleState\");\n            whenReadable(state, LOCK_REASON_FENCE);\n        }\n    } else if (state->buffer && state->buffer->isSynchronous()) {\n        // synchronous (shm) buffers can be read immediately\n        m_stateQueue.unlock(state, LOCK_REASON_FENCE);\n    } else if (state->buffer && state->buffer->m_syncFd.isValid()) {\n        // async buffer and is dmabuf, then we can wait on implicit fences\n        g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); });\n    } else {\n        // state commit without a buffer.\n        m_stateQueue.tryProcess();\n    }\n}\n\nvoid CWLSurfaceResource::commitState(SSurfaceState& state) {\n    // TODO might be incorrect. needed for VRR with FIFO to avoid same buffer extra frames for second commit when it's used in this way:\n    // wp_fifo_v1#43.set_barrier()\n    // wp_fifo_v1#43.wait_barrier()\n    // wl_surface#3.commit()\n    // wp_fifo_v1#43.wait_barrier()\n    // wl_surface#3.commit()\n    if (!state.updated.all && m_mapped && state.fifoScheduled)\n        return;\n\n    auto lastTexture = m_current.texture;\n    m_current.updateFrom(state);\n\n    if (m_current.buffer) {\n        if (m_current.buffer->isSynchronous())\n            m_current.updateSynchronousTexture(lastTexture);\n\n        // if the surface is a cursor, update the shm buffer\n        // TODO: don't update the entire texture\n        if (m_role->role() == SURFACE_ROLE_CURSOR)\n            updateCursorShm(m_current.accumulateBufferDamage());\n    }\n\n    if (m_current.texture)\n        m_current.texture->m_transform = Math::wlTransformToHyprutils(m_current.transform);\n\n    if (m_role->role() == SURFACE_ROLE_SUBSURFACE) {\n        auto subsurface = sc<CSubsurfaceRole*>(m_role.get())->m_subsurface.lock();\n        if (subsurface->m_sync)\n            return;\n\n        m_events.commit.emit();\n    } else {\n        // send commit to all synced surfaces in this tree.\n        breadthfirst(\n            [](SP<CWLSurfaceResource> surf, const Vector2D& offset, void* data) {\n                if (surf->m_role->role() == SURFACE_ROLE_SUBSURFACE) {\n                    auto subsurface = sc<CSubsurfaceRole*>(surf->m_role.get())->m_subsurface.lock();\n                    if (!subsurface->m_sync)\n                        return;\n                }\n                surf->m_events.commit.emit();\n            },\n            nullptr);\n    }\n\n    // release the buffer if it's synchronous (SHM) as updateSynchronousTexture() has copied the buffer data to a GPU tex\n    // if it doesn't have a role, we can't release it yet, in case it gets turned into a cursor.\n    if (m_current.buffer && m_current.buffer->isSynchronous() && m_role->role() != SURFACE_ROLE_UNASSIGNED)\n        dropCurrentBuffer();\n}\n\nPImageDescription CWLSurfaceResource::getPreferredImageDescription() {\n    static const auto PFORCE_HDR = CConfigValue<Hyprlang::INT>(\"quirks:prefer_hdr\");\n    const auto        WINDOW     = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr;\n\n    if (*PFORCE_HDR == 1 || (*PFORCE_HDR == 2 && m_hlSurface && WINDOW && WINDOW->m_class == \"gamescope\"))\n        return g_pCompositor->getHDRImageDescription();\n\n    auto parent = m_self;\n    if (parent->m_role->role() == SURFACE_ROLE_SUBSURFACE) {\n        auto subsurface = sc<CSubsurfaceRole*>(parent->m_role.get())->m_subsurface.lock();\n        parent          = subsurface->t1Parent();\n    }\n    WP<CMonitor> monitor;\n    if (parent->m_enteredOutputs.size() == 1)\n        monitor = parent->m_enteredOutputs[0];\n    else if (m_hlSurface.valid() && WINDOW)\n        monitor = WINDOW->m_monitor;\n\n    return monitor ? monitor->m_imageDescription : g_pCompositor->getPreferredImageDescription();\n}\n\nvoid CWLSurfaceResource::sortSubsurfaces() {\n    std::ranges::sort(m_subsurfaces, [](const auto& a, const auto& b) { return a->m_zIndex < b->m_zIndex; });\n\n    // find the first non-negative index. We will preserve negativity: e.g. -2, -1, 1, 2\n    int firstNonNegative = -1;\n    for (size_t i = 0; i < m_subsurfaces.size(); ++i) {\n        if (m_subsurfaces.at(i)->m_zIndex >= 0) {\n            firstNonNegative = i;\n            break;\n        }\n    }\n\n    if (firstNonNegative == -1)\n        firstNonNegative = m_subsurfaces.size();\n\n    for (size_t i = firstNonNegative; i < m_subsurfaces.size(); ++i) {\n        m_subsurfaces.at(i)->m_zIndex = i - firstNonNegative;\n    }\n\n    for (int i = 0; i < firstNonNegative; ++i) {\n        m_subsurfaces.at(i)->m_zIndex = -firstNonNegative + i;\n    }\n}\n\nbool CWLSurfaceResource::hasVisibleSubsurface() {\n    for (auto const& subsurface : m_subsurfaces) {\n        if (!subsurface || !subsurface->m_surface)\n            continue;\n\n        const auto& surf = subsurface->m_surface;\n        if (surf->m_current.size.x > 0 && surf->m_current.size.y > 0)\n            return true;\n    }\n\n    return false;\n}\n\nbool CWLSurfaceResource::isTearing() {\n    if (m_enteredOutputs.empty() && m_hlSurface) {\n        for (auto& m : g_pCompositor->m_monitors) {\n            if (!m || !m->m_enabled)\n                continue;\n\n            auto box = m_hlSurface->getSurfaceBoxGlobal();\n            if (box && !box->intersection({m->m_position, m->m_size}).empty()) {\n                if (m->m_tearingState.activelyTearing)\n                    return true;\n            }\n        }\n    } else {\n        for (auto& m : m_enteredOutputs) {\n            if (!m)\n                continue;\n\n            if (m->m_tearingState.activelyTearing)\n                return true;\n        }\n    }\n    return false;\n}\n\nvoid CWLSurfaceResource::updateCursorShm(CRegion damage) {\n    if (damage.empty())\n        return;\n\n    auto buf = m_current.buffer ? m_current.buffer : SP<IHLBuffer>{};\n\n    if UNLIKELY (!buf)\n        return;\n\n    auto& shmData  = CCursorSurfaceRole::cursorPixelData(m_self.lock());\n    auto  shmAttrs = buf->shm();\n\n    if (!shmAttrs.success) {\n        LOGM(Log::TRACE, \"updateCursorShm: ignoring, not a shm buffer\");\n        return;\n    }\n\n    damage.intersect(CBox{0, 0, buf->size.x, buf->size.y});\n\n    // no need to end, shm.\n    auto [pixelData, fmt, bufLen] = buf->beginDataPtr(0);\n\n    shmData.resize(bufLen);\n\n    if (const auto RECTS = damage.getRects(); RECTS.size() == 1 && RECTS.at(0).x2 == buf->size.x && RECTS.at(0).y2 == buf->size.y)\n        memcpy(shmData.data(), pixelData, bufLen);\n    else {\n        damage.forEachRect([&pixelData, &shmData](const auto& box) {\n            for (auto y = box.y1; y < box.y2; ++y) {\n                // bpp is 32 INSALLAH\n                auto begin = 4 * box.y1 * (box.x2 - box.x1) + box.x1;\n                auto len   = 4 * (box.x2 - box.x1);\n                memcpy(shmData.data() + begin, pixelData + begin, len);\n            }\n        });\n    }\n}\n\nvoid CWLSurfaceResource::presentFeedback(const Time::steady_tp& when, PHLMONITOR pMonitor, bool discarded) {\n    frame(when);\n    auto FEEDBACK = makeUnique<CQueuedPresentationData>(m_self.lock());\n    FEEDBACK->attachMonitor(pMonitor);\n    if (discarded)\n        FEEDBACK->discarded();\n    else {\n        FEEDBACK->presented();\n        if (!pMonitor->m_lastScanout.expired()) {\n            const auto WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr;\n            if (WINDOW == pMonitor->m_lastScanout)\n                FEEDBACK->setPresentationType(true);\n        }\n    }\n    PROTO::presentation->queueData(std::move(FEEDBACK));\n}\n\nCWLCompositorResource::CWLCompositorResource(SP<CWlCompositor> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlCompositor* r) { PROTO::compositor->destroyResource(this); });\n\n    m_resource->setCreateSurface([](CWlCompositor* r, uint32_t id) {\n        const auto RESOURCE = PROTO::compositor->m_surfaces.emplace_back(makeShared<CWLSurfaceResource>(makeShared<CWlSurface>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::compositor->m_surfaces.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self       = RESOURCE;\n        RESOURCE->m_stateQueue = CSurfaceStateQueue(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New wl_surface with id {} at {:x}\", id, (uintptr_t)RESOURCE.get());\n\n        PROTO::compositor->m_events.newSurface.emit(RESOURCE);\n    });\n\n    m_resource->setCreateRegion([](CWlCompositor* r, uint32_t id) {\n        const auto RESOURCE = PROTO::compositor->m_regions.emplace_back(makeShared<CWLRegionResource>(makeShared<CWlRegion>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::compositor->m_regions.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        LOGM(Log::DEBUG, \"New wl_region with id {} at {:x}\", id, (uintptr_t)RESOURCE.get());\n    });\n}\n\nbool CWLCompositorResource::good() {\n    return m_resource->resource();\n}\n\nCWLCompositorProtocol::CWLCompositorProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CWLCompositorProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CWLCompositorResource>(makeShared<CWlCompositor>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CWLCompositorProtocol::destroyResource(CWLCompositorResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLCompositorProtocol::destroyResource(CWLSurfaceResource* resource) {\n    std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLCompositorProtocol::destroyResource(CWLRegionResource* resource) {\n    std::erase_if(m_regions, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLCompositorProtocol::forEachSurface(std::function<void(SP<CWLSurfaceResource>)> fn) {\n    for (auto& surf : m_surfaces) {\n        fn(surf);\n    }\n}\n"
  },
  {
    "path": "src/protocols/core/Compositor.hpp",
    "content": "#pragma once\n\n/*\n    Implementations for:\n     - wl_compositor\n     - wl_surface\n     - wl_region\n     - wl_callback\n*/\n\n#include <vector>\n#include <queue>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include \"../../render/Texture.hpp\"\n#include \"../types/SurfaceStateQueue.hpp\"\n#include \"wayland.hpp\"\n#include \"../../desktop/view/WLSurface.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n#include \"../types/Buffer.hpp\"\n#include \"../../helpers/cm/ColorManagement.hpp\"\n#include \"../types/SurfaceRole.hpp\"\n#include \"../types/SurfaceState.hpp\"\n\nclass CWLOutputResource;\nclass CMonitor;\nclass CWLSurfaceResource;\nclass CWLSubsurfaceResource;\nclass CViewportResource;\nclass CDRMSyncobjSurfaceResource;\nclass CFifoResource;\nclass CCommitTimerResource;\nclass CColorManagementSurface;\nclass CContentType;\n\nclass CWLCallbackResource {\n  public:\n    CWLCallbackResource(SP<CWlCallback>&& resource_);\n    ~CWLCallbackResource() noexcept = default;\n    // disable copy\n    CWLCallbackResource(const CWLCallbackResource&)            = delete;\n    CWLCallbackResource& operator=(const CWLCallbackResource&) = delete;\n\n    // allow move\n    CWLCallbackResource(CWLCallbackResource&&) noexcept            = default;\n    CWLCallbackResource& operator=(CWLCallbackResource&&) noexcept = default;\n\n    bool                 good();\n    void                 send(const Time::steady_tp& now);\n\n  private:\n    SP<CWlCallback> m_resource;\n};\n\nclass CWLRegionResource {\n  public:\n    CWLRegionResource(SP<CWlRegion> resource_);\n    static SP<CWLRegionResource> fromResource(wl_resource* res);\n\n    bool                         good();\n\n    CRegion                      m_region;\n    WP<CWLRegionResource>        m_self;\n\n  private:\n    SP<CWlRegion> m_resource;\n};\n\nclass CWLSurfaceResource {\n  public:\n    CWLSurfaceResource(SP<CWlSurface> resource_);\n    ~CWLSurfaceResource();\n\n    static SP<CWLSurfaceResource> fromResource(wl_resource* res);\n\n    bool                          good();\n    wl_client*                    client();\n    void                          enter(PHLMONITOR monitor);\n    void                          leave(PHLMONITOR monitor);\n    void                          sendPreferredTransform(wl_output_transform t);\n    void                          sendPreferredScale(int32_t scale);\n    void                          frame(const Time::steady_tp& now);\n    uint32_t                      id();\n    void                          map();\n    void                          unmap();\n    void                          error(int code, const std::string& str);\n    SP<CWlSurface>                getResource();\n    CBox                          extends();\n    void                          resetRole();\n\n    struct {\n        CSignalT<>                          precommit;    // before commit\n        CSignalT<WP<SSurfaceState>>         stateCommit;  // when placing state in queue\n        CSignalT<WP<SSurfaceState>>         stateCommit2; // when placing state in queue used for commit timing so we apply fifo/fences first.\n        CSignalT<>                          commit;       // after commit\n        CSignalT<>                          map;\n        CSignalT<>                          unmap;\n        CSignalT<SP<CWLSubsurfaceResource>> newSubsurface;\n        CSignalT<>                          destroy;\n        CSignalT<SP<CMonitor>>              enter;\n        CSignalT<SP<CMonitor>>              leave;\n    } m_events;\n\n    SSurfaceState                          m_current;\n    SSurfaceState                          m_pending;\n    CSurfaceStateQueue                     m_stateQueue;\n\n    WP<CWLSurfaceResource>                 m_self;\n    WP<Desktop::View::CWLSurface>          m_hlSurface;\n    std::vector<PHLMONITORREF>             m_enteredOutputs;\n    bool                                   m_mapped = false;\n    std::vector<WP<CWLSubsurfaceResource>> m_subsurfaces;\n    SP<ISurfaceRole>                       m_role;\n    WP<CDRMSyncobjSurfaceResource>         m_syncobj;     // may not be present\n    WP<CFifoResource>                      m_fifo;        // may not be present\n    WP<CCommitTimerResource>               m_commitTimer; // may not be present\n    WP<CColorManagementSurface>            m_colorManagement;\n    WP<CContentType>                       m_contentType;\n\n    void                                   breadthfirst(std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);\n    SP<CWLSurfaceResource>                 findFirstPreorder(std::function<bool(SP<CWLSurfaceResource>)> fn);\n    SP<CWLSurfaceResource>                 findWithCM();\n    void                                   presentFeedback(const Time::steady_tp& when, PHLMONITOR pMonitor, bool discarded = false);\n    void                                   scheduleState(WP<SSurfaceState> state);\n    void                                   commitState(SSurfaceState& state);\n    NColorManagement::PImageDescription    getPreferredImageDescription();\n    void                                   sortSubsurfaces();\n    bool                                   hasVisibleSubsurface();\n    bool                                   isTearing();\n\n    // returns a pair: found surface (null if not found) and surface local coords.\n    // localCoords param is relative to 0,0 of this surface\n    std::pair<SP<CWLSurfaceResource>, Vector2D> at(const Vector2D& localCoords, bool allowsInput = false);\n\n  private:\n    SP<CWlSurface>         m_resource;\n    wl_client*             m_client = nullptr;\n\n    void                   destroy();\n    void                   releaseBuffers(bool onlyCurrent = true);\n    void                   dropPendingBuffer();\n    void                   dropCurrentBuffer();\n    void                   bfHelper(std::vector<SP<CWLSurfaceResource>> const& nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);\n    SP<CWLSurfaceResource> findFirstPreorderHelper(SP<CWLSurfaceResource> root, std::function<bool(SP<CWLSurfaceResource>)> fn);\n    void                   updateCursorShm(CRegion damage = CBox{0, 0, INT16_MAX, INT16_MAX});\n\n    friend class CWLPointerResource;\n};\n\nclass CWLCompositorResource {\n  public:\n    CWLCompositorResource(SP<CWlCompositor> resource_);\n\n    bool good();\n\n  private:\n    SP<CWlCompositor> m_resource;\n};\n\nclass CWLCompositorProtocol : public IWaylandProtocol {\n  public:\n    CWLCompositorProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    void         forEachSurface(std::function<void(SP<CWLSurfaceResource>)> fn);\n\n    struct {\n        CSignalT<SP<CWLSurfaceResource>> newSurface;\n    } m_events;\n\n  private:\n    void destroyResource(CWLCompositorResource* resource);\n    void destroyResource(CWLSurfaceResource* resource);\n    void destroyResource(CWLRegionResource* resource);\n\n    //\n    std::vector<SP<CWLCompositorResource>> m_managers;\n    std::vector<SP<CWLSurfaceResource>>    m_surfaces;\n    std::vector<SP<CWLRegionResource>>     m_regions;\n\n    friend class CWLSurfaceResource;\n    friend class CWLCompositorResource;\n    friend class CWLRegionResource;\n    friend class CWLCallbackResource;\n};\n\nnamespace PROTO {\n    inline UP<CWLCompositorProtocol> compositor;\n};\n"
  },
  {
    "path": "src/protocols/core/DataDevice.cpp",
    "content": "#include \"DataDevice.hpp\"\n#include <algorithm>\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../managers/PointerManager.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../render/pass/TexPassElement.hpp\"\n#include \"Seat.hpp\"\n#include \"Compositor.hpp\"\n#include \"../../xwayland/XWayland.hpp\"\n#include \"../../xwayland/Server.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../managers/cursor/CursorShapeOverrideController.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../xwayland/Dnd.hpp\"\n#include \"../../event/EventBus.hpp\"\nusing namespace Hyprutils::OS;\n\nCWLDataOfferResource::CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_) : m_source(source_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setDestroy([this](CWlDataOffer* r) { PROTO::data->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlDataOffer* r) { PROTO::data->destroyResource(this); });\n\n    m_resource->setAccept([this](CWlDataOffer* r, uint32_t serial, const char* mime) {\n        if (!m_source) {\n            LOGM(Log::WARN, \"Possible bug: Accept on an offer w/o a source\");\n            return;\n        }\n\n        if (m_dead) {\n            LOGM(Log::WARN, \"Possible bug: Accept on an offer that's dead\");\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"Offer {:x} accepts data from source {:x} with mime {}\", (uintptr_t)this, (uintptr_t)m_source.get(), mime ? mime : \"null\");\n\n        m_source->accepted(mime ? mime : \"\");\n        m_accepted = mime;\n    });\n\n    m_resource->setReceive([this](CWlDataOffer* r, const char* mime, int fd) {\n        CFileDescriptor sendFd{fd};\n        if (!m_source) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer w/o a source\");\n            return;\n        }\n\n        if (m_dead) {\n            LOGM(Log::WARN, \"Possible bug: Receive on an offer that's dead\");\n            return;\n        }\n\n        LOGM(Log::DEBUG, \"Offer {:x} asks to send data from source {:x}\", (uintptr_t)this, (uintptr_t)m_source.get());\n\n        if (!m_accepted) {\n            LOGM(Log::WARN, \"Offer was never accepted, sending accept first\");\n            m_source->accepted(mime ? mime : \"\");\n        }\n\n        m_source->send(mime ? mime : \"\", std::move(sendFd));\n\n        m_recvd = true;\n\n        // if (source->hasDnd())\n        //     PROTO::data->completeDrag();\n    });\n\n    m_resource->setFinish([this](CWlDataOffer* r) {\n        m_dead = true;\n        if (!m_source || !m_recvd || !m_accepted)\n            PROTO::data->abortDrag();\n        else\n            PROTO::data->completeDrag();\n    });\n}\n\nCWLDataOfferResource::~CWLDataOfferResource() {\n    if (!m_source || !m_source->hasDnd() || m_dead)\n        return;\n\n    m_source->sendDndFinished();\n}\n\nbool CWLDataOfferResource::good() {\n    return m_resource->resource();\n}\n\nvoid CWLDataOfferResource::sendData() {\n    if (!m_source)\n        return;\n\n    const auto SOURCEACTIONS = m_source->actions();\n\n    if (m_resource->version() >= 3 && SOURCEACTIONS > 0) {\n        m_resource->sendSourceActions(SOURCEACTIONS);\n        if (SOURCEACTIONS & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)\n            m_resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE);\n        else if (SOURCEACTIONS & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)\n            m_resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);\n        else {\n            LOGM(Log::ERR, \"Client bug? dnd source has no action move or copy. Sending move, f this.\");\n            m_resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE);\n        }\n    }\n\n    for (auto const& m : m_source->mimes()) {\n        LOGM(Log::DEBUG, \" | offer {:x} supports mime {}\", (uintptr_t)this, m);\n        m_resource->sendOffer(m.c_str());\n    }\n}\n\neDataSourceType CWLDataOfferResource::type() {\n    return DATA_SOURCE_TYPE_WAYLAND;\n}\n\nSP<CWLDataOfferResource> CWLDataOfferResource::getWayland() {\n    return m_self.lock();\n}\n\nSP<CX11DataOffer> CWLDataOfferResource::getX11() {\n    return nullptr;\n}\n\nSP<IDataSource> CWLDataOfferResource::getSource() {\n    return m_source.lock();\n}\n\nCWLDataSourceResource::CWLDataSourceResource(SP<CWlDataSource> resource_, SP<CWLDataDeviceResource> device_) : m_device(device_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setDestroy([this](CWlDataSource* r) {\n        m_events.destroy.emit();\n        PROTO::data->onDestroyDataSource(m_self);\n        PROTO::data->destroyResource(this);\n    });\n    m_resource->setOnDestroy([this](CWlDataSource* r) {\n        m_events.destroy.emit();\n        PROTO::data->onDestroyDataSource(m_self);\n        PROTO::data->destroyResource(this);\n    });\n\n    m_resource->setOffer([this](CWlDataSource* r, const char* mime) { m_mimeTypes.emplace_back(mime); });\n    m_resource->setSetActions([this](CWlDataSource* r, uint32_t a) {\n        LOGM(Log::DEBUG, \"DataSource {:x} actions {}\", (uintptr_t)this, a);\n        m_supportedActions = a;\n    });\n}\n\nCWLDataSourceResource::~CWLDataSourceResource() {\n    m_events.destroy.emit();\n    PROTO::data->onDestroyDataSource(m_self);\n}\n\nSP<CWLDataSourceResource> CWLDataSourceResource::fromResource(wl_resource* res) {\n    auto data = sc<CWLDataSourceResource*>(sc<CWlDataSource*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CWLDataSourceResource::good() {\n    return m_resource->resource();\n}\n\nvoid CWLDataSourceResource::accepted(const std::string& mime) {\n    if (mime.empty()) {\n        m_resource->sendTarget(nullptr);\n        return;\n    }\n\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) {\n        LOGM(Log::ERR, \"Compositor/App bug: CWLDataSourceResource::sendAccepted with non-existent mime\");\n        return;\n    }\n\n    m_resource->sendTarget(mime.c_str());\n}\n\nstd::vector<std::string> CWLDataSourceResource::mimes() {\n    return m_mimeTypes;\n}\n\nvoid CWLDataSourceResource::send(const std::string& mime, CFileDescriptor fd) {\n    if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) {\n        LOGM(Log::ERR, \"Compositor/App bug: CWLDataSourceResource::sendAskSend with non-existent mime\");\n        return;\n    }\n\n    m_resource->sendSend(mime.c_str(), fd.get());\n}\n\nvoid CWLDataSourceResource::cancelled() {\n    m_resource->sendCancelled();\n}\n\nbool CWLDataSourceResource::hasDnd() {\n    return m_dnd;\n}\n\nbool CWLDataSourceResource::dndDone() {\n    return m_dndSuccess;\n}\n\nvoid CWLDataSourceResource::error(uint32_t code, const std::string& msg) {\n    m_resource->error(code, msg);\n}\n\nvoid CWLDataSourceResource::sendDndDropPerformed() {\n    if (m_resource->version() < 3)\n        return;\n    m_resource->sendDndDropPerformed();\n    m_dropped = true;\n}\n\nvoid CWLDataSourceResource::sendDndFinished() {\n    if (m_resource->version() < 3)\n        return;\n    m_resource->sendDndFinished();\n}\n\nvoid CWLDataSourceResource::sendDndAction(wl_data_device_manager_dnd_action a) {\n    if (m_resource->version() < 3)\n        return;\n    m_resource->sendAction(a);\n}\n\nuint32_t CWLDataSourceResource::actions() {\n    return m_supportedActions;\n}\n\neDataSourceType CWLDataSourceResource::type() {\n    return DATA_SOURCE_TYPE_WAYLAND;\n}\n\nCWLDataDeviceResource::CWLDataDeviceResource(SP<CWlDataDevice> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](CWlDataDevice* r) { PROTO::data->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlDataDevice* r) { PROTO::data->destroyResource(this); });\n\n    m_client = m_resource->client();\n\n    m_resource->setSetSelection([](CWlDataDevice* r, wl_resource* sourceR, uint32_t serial) {\n        auto source = sourceR ? CWLDataSourceResource::fromResource(sourceR) : CSharedPointer<CWLDataSourceResource>{};\n        if (!source) {\n            LOGM(Log::DEBUG, \"Reset selection received\");\n            g_pSeatManager->setCurrentSelection(nullptr);\n            return;\n        }\n\n        if (source && source->m_used)\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        g_pSeatManager->setCurrentSelection(source);\n    });\n\n    m_resource->setStartDrag([](CWlDataDevice* r, wl_resource* sourceR, wl_resource* origin, wl_resource* icon, uint32_t serial) {\n        auto source = CWLDataSourceResource::fromResource(sourceR);\n        if (!source) {\n            LOGM(Log::ERR, \"No source in drag\");\n            return;\n        }\n\n        if (source && source->m_used)\n            LOGM(Log::WARN, \"setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this.\");\n\n        source->markUsed();\n\n        source->m_dnd = true;\n\n        PROTO::data->initiateDrag(source, icon ? CWLSurfaceResource::fromResource(icon) : nullptr, CWLSurfaceResource::fromResource(origin));\n    });\n}\n\nbool CWLDataDeviceResource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CWLDataDeviceResource::client() {\n    return m_client;\n}\n\nvoid CWLDataDeviceResource::sendDataOffer(SP<IDataOffer> offer) {\n    if (!offer)\n        m_resource->sendDataOfferRaw(nullptr);\n    else if (const auto WL = offer->getWayland(); WL)\n        m_resource->sendDataOffer(WL->m_resource.get());\n    //FIXME: X11\n}\n\nvoid CWLDataDeviceResource::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) {\n    if (const auto WL = offer->getWayland(); WL)\n        m_resource->sendEnterRaw(serial, surf->getResource()->resource(), wl_fixed_from_double(local.x), wl_fixed_from_double(local.y), WL->m_resource->resource());\n\n    m_entered = surf;\n\n    // FIXME: X11\n}\n\nvoid CWLDataDeviceResource::sendLeave() {\n    if (!m_entered)\n        return;\n\n    m_entered.reset();\n    m_resource->sendLeave();\n}\n\nvoid CWLDataDeviceResource::sendMotion(uint32_t timeMs, const Vector2D& local) {\n    m_resource->sendMotion(timeMs, wl_fixed_from_double(local.x), wl_fixed_from_double(local.y));\n}\n\nvoid CWLDataDeviceResource::sendDrop() {\n    m_resource->sendDrop();\n}\n\nvoid CWLDataDeviceResource::sendSelection(SP<IDataOffer> offer) {\n    if (!offer)\n        m_resource->sendSelectionRaw(nullptr);\n    else if (const auto WL = offer->getWayland(); WL)\n        m_resource->sendSelection(WL->m_resource.get());\n}\n\neDataSourceType CWLDataDeviceResource::type() {\n    return DATA_SOURCE_TYPE_WAYLAND;\n}\n\nSP<CWLDataDeviceResource> CWLDataDeviceResource::getWayland() {\n    return m_self.lock();\n}\n\nSP<CX11DataDevice> CWLDataDeviceResource::getX11() {\n    return nullptr;\n}\n\nCWLDataDeviceManagerResource::CWLDataDeviceManagerResource(SP<CWlDataDeviceManager> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlDataDeviceManager* r) { PROTO::data->destroyResource(this); });\n\n    m_resource->setCreateDataSource([this](CWlDataDeviceManager* r, uint32_t id) {\n        std::erase_if(m_sources, [](const auto& e) { return e.expired(); });\n\n        const auto RESOURCE = PROTO::data->m_sources.emplace_back(makeShared<CWLDataSourceResource>(makeShared<CWlDataSource>(r->client(), r->version(), id), m_device.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::data->m_sources.pop_back();\n            return;\n        }\n\n        if (!m_device)\n            LOGM(Log::WARN, \"New data source before a device was created\");\n\n        RESOURCE->m_self = RESOURCE;\n\n        m_sources.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New data source bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n\n    m_resource->setGetDataDevice([this](CWlDataDeviceManager* r, uint32_t id, wl_resource* seat) {\n        const auto RESOURCE = PROTO::data->m_devices.emplace_back(makeShared<CWLDataDeviceResource>(makeShared<CWlDataDevice>(r->client(), r->version(), id)));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::data->m_devices.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        for (auto const& s : m_sources) {\n            if (!s)\n                continue;\n            s->m_device = RESOURCE;\n        }\n\n        LOGM(Log::DEBUG, \"New data device bound at {:x}\", (uintptr_t)RESOURCE.get());\n    });\n}\n\nbool CWLDataDeviceManagerResource::good() {\n    return m_resource->resource();\n}\n\nCWLDataDeviceProtocol::CWLDataDeviceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    g_pEventLoopManager->doLater([this]() {\n        m_listeners.onKeyboardFocusChange   = g_pSeatManager->m_events.keyboardFocusChange.listen([this] { onKeyboardFocus(); });\n        m_listeners.onDndPointerFocusChange = g_pSeatManager->m_events.dndPointerFocusChange.listen([this] { onDndPointerFocus(); });\n    });\n}\n\nvoid CWLDataDeviceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CWLDataDeviceManagerResource>(makeShared<CWlDataDeviceManager>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New datamgr resource bound at {:x}\", (uintptr_t)RESOURCE.get());\n}\n\nvoid CWLDataDeviceProtocol::destroyResource(CWLDataDeviceManagerResource* seat) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == seat; });\n}\n\nvoid CWLDataDeviceProtocol::destroyResource(CWLDataDeviceResource* resource) {\n    std::erase_if(m_devices, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLDataDeviceProtocol::destroyResource(CWLDataSourceResource* resource) {\n    std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLDataDeviceProtocol::destroyResource(CWLDataOfferResource* resource) {\n    std::erase_if(m_offers, [&](const auto& other) { return other.get() == resource; });\n}\n\nSP<IDataDevice> CWLDataDeviceProtocol::dataDeviceForClient(wl_client* c) {\n#ifndef NO_XWAYLAND\n    if (g_pXWayland && g_pXWayland->m_server && c == g_pXWayland->m_server->m_xwaylandClient)\n        return g_pXWayland->m_wm->getDataDevice();\n#endif\n\n    auto it = std::ranges::find_if(m_devices, [c](const auto& e) { return e->client() == c; });\n    if (it == m_devices.end())\n        return nullptr;\n    return *it;\n}\n\nvoid CWLDataDeviceProtocol::sendSelectionToDevice(SP<IDataDevice> dev, SP<IDataSource> sel) {\n    if (!sel) {\n        dev->sendSelection(nullptr);\n        return;\n    }\n\n    SP<IDataOffer> offer;\n\n    if (const auto WL = dev->getWayland(); WL) {\n        const auto OFFER = m_offers.emplace_back(makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(WL->m_resource->client(), WL->m_resource->version(), 0), sel));\n        if UNLIKELY (!OFFER->good()) {\n            WL->m_resource->noMemory();\n            m_offers.pop_back();\n            return;\n        }\n        OFFER->m_source = sel;\n        OFFER->m_self   = OFFER;\n        offer           = OFFER;\n    }\n#ifndef NO_XWAYLAND\n    else if (const auto X11 = dev->getX11(); X11)\n        offer = g_pXWayland->m_wm->createX11DataOffer(g_pSeatManager->m_state.keyboardFocus.lock(), sel);\n#endif\n\n    if UNLIKELY (!offer) {\n        LOGM(Log::ERR, \"No offer could be created in sendSelectionToDevice\");\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New {} offer {:x} for data source {:x}\", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? \"wayland\" : \"X11\", (uintptr_t)offer.get(), (uintptr_t)sel.get());\n\n    dev->sendDataOffer(offer);\n    if (const auto WL = offer->getWayland(); WL)\n        WL->sendData();\n    dev->sendSelection(offer);\n}\n\nvoid CWLDataDeviceProtocol::onDestroyDataSource(WP<CWLDataSourceResource> source) {\n    if (m_dnd.currentSource == source)\n        abortDrag();\n}\n\nvoid CWLDataDeviceProtocol::setSelection(SP<IDataSource> source) {\n    for (auto const& o : m_offers) {\n        if (o->m_source && o->m_source->hasDnd())\n            continue;\n        o->m_dead = true;\n    }\n\n    if (!source) {\n        LOGM(Log::DEBUG, \"resetting selection\");\n\n        if (!g_pSeatManager->m_state.keyboardFocusResource)\n            return;\n\n        auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.keyboardFocusResource->client());\n        if (DESTDEVICE && DESTDEVICE->type() == DATA_SOURCE_TYPE_WAYLAND)\n            sendSelectionToDevice(DESTDEVICE, nullptr);\n\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New selection for data source {:x}\", (uintptr_t)source.get());\n\n    if (!g_pSeatManager->m_state.keyboardFocusResource)\n        return;\n\n    auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.keyboardFocusResource->client());\n\n    if (!DESTDEVICE) {\n        LOGM(Log::DEBUG, \"CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device\");\n        return;\n    }\n\n    if (DESTDEVICE->type() != DATA_SOURCE_TYPE_WAYLAND) {\n        LOGM(Log::DEBUG, \"CWLDataDeviceProtocol::setSelection: ignoring X11 data device\");\n        return;\n    }\n\n    sendSelectionToDevice(DESTDEVICE, source);\n}\n\nvoid CWLDataDeviceProtocol::updateSelection() {\n    if (!g_pSeatManager->m_state.keyboardFocusResource)\n        return;\n\n    auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.keyboardFocusResource->client());\n\n    if (!DESTDEVICE) {\n        LOGM(Log::DEBUG, \"CWLDataDeviceProtocol::onKeyboardFocus: cannot send selection to a client without a data_device\");\n        return;\n    }\n\n    sendSelectionToDevice(DESTDEVICE, g_pSeatManager->m_selection.currentSelection.lock());\n}\n\nvoid CWLDataDeviceProtocol::onKeyboardFocus() {\n    for (auto const& o : m_offers) {\n        if (o->m_source && o->m_source->hasDnd())\n            continue;\n        o->m_dead = true;\n    }\n\n    updateSelection();\n}\n\nvoid CWLDataDeviceProtocol::onDndPointerFocus() {\n    for (auto const& o : m_offers) {\n        if (o->m_source && !o->m_source->hasDnd())\n            continue;\n        o->m_dead = true;\n    }\n\n    updateDrag();\n}\n\nvoid CWLDataDeviceProtocol::initiateDrag(WP<CWLDataSourceResource> currentSource, SP<CWLSurfaceResource> dragSurface, SP<CWLSurfaceResource> origin) {\n\n    if (m_dnd.currentSource) {\n        LOGM(Log::WARN, \"New drag started while old drag still active??\");\n        abortDrag();\n    }\n\n    Cursor::overrideController->setOverride(\"grabbing\", Cursor::CURSOR_OVERRIDE_DND);\n    m_dnd.overriddenCursor = true;\n\n    LOGM(Log::DEBUG, \"initiateDrag: source {:x}, surface: {:x}, origin: {:x}\", (uintptr_t)currentSource.get(), (uintptr_t)dragSurface, (uintptr_t)origin);\n\n    currentSource->m_used = true;\n\n    m_dnd.currentSource = currentSource;\n    m_dnd.originSurface = origin;\n    m_dnd.dndSurface    = dragSurface;\n    if (dragSurface) {\n        m_dnd.dndSurfaceDestroy = dragSurface->m_events.destroy.listen([this] { abortDrag(); });\n        m_dnd.dndSurfaceCommit  = dragSurface->m_events.commit.listen([this] {\n            if (m_dnd.dndSurface->m_current.texture && !m_dnd.dndSurface->m_mapped) {\n                m_dnd.dndSurface->map();\n                return;\n            }\n\n            if (m_dnd.dndSurface->m_current.texture <= 0 && m_dnd.dndSurface->m_mapped) {\n                m_dnd.dndSurface->unmap();\n                return;\n            }\n        });\n    }\n\n    m_dnd.mouseButton = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent e, Event::SCallbackInfo&) {\n        if (e.state == WL_POINTER_BUTTON_STATE_RELEASED) {\n            LOGM(Log::DEBUG, \"Dropping drag on mouseUp\");\n            dropDrag();\n        }\n    });\n\n    m_dnd.touchUp = Event::bus()->m_events.input.touch.up.listen([this](ITouch::SUpEvent e, Event::SCallbackInfo&) {\n        LOGM(Log::DEBUG, \"Dropping drag on touchUp\");\n        dropDrag();\n    });\n\n    m_dnd.tabletTip = Event::bus()->m_events.input.tablet.tip.listen([this](CTablet::STipEvent e, Event::SCallbackInfo&) {\n        if (!e.in) {\n            LOGM(Log::DEBUG, \"Dropping drag on tablet tipUp\");\n            dropDrag();\n        }\n    });\n\n    m_dnd.mouseMove = Event::bus()->m_events.input.mouse.move.listen([this](Vector2D pos, Event::SCallbackInfo&) {\n        if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) {\n            auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock());\n\n            if (!surf)\n                return;\n\n            const auto box = surf->getSurfaceBoxGlobal();\n\n            if (!box.has_value())\n                return;\n\n            m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), pos - box->pos());\n            LOGM(Log::DEBUG, \"Drag motion {}\", pos - box->pos());\n        }\n    });\n\n    m_dnd.touchMove = Event::bus()->m_events.input.touch.motion.listen([this](ITouch::SMotionEvent e, Event::SCallbackInfo&) {\n        if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) {\n            auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock());\n\n            if (!surf)\n                return;\n\n            const auto box = surf->getSurfaceBoxGlobal();\n\n            if (!box.has_value())\n                return;\n\n            m_dnd.focusedDevice->sendMotion(e.timeMs, e.pos);\n            LOGM(Log::DEBUG, \"Drag motion {}\", e.pos);\n        }\n    });\n\n    // unfocus the pointer from the surface, this is part of \"\"\"standard\"\"\" wayland procedure and gtk will freak out if this isn't happening.\n    // BTW, the spec does NOT require this explicitly...\n    // Fuck you gtk.\n    const auto LASTDNDFOCUS = g_pSeatManager->m_state.dndPointerFocus;\n    g_pSeatManager->setPointerFocus(nullptr, {});\n    g_pSeatManager->m_state.dndPointerFocus = LASTDNDFOCUS;\n\n    // make a new offer, etc\n    updateDrag();\n}\n\nvoid CWLDataDeviceProtocol::updateDrag() {\n    if (!dndActive())\n        return;\n\n    if (m_dnd.focusedDevice)\n        m_dnd.focusedDevice->sendLeave();\n\n    if (!g_pSeatManager->m_state.dndPointerFocus)\n        return;\n\n    m_dnd.focusedDevice = dataDeviceForClient(g_pSeatManager->m_state.dndPointerFocus->client());\n\n    if (!m_dnd.focusedDevice)\n        return;\n\n    SP<IDataOffer> offer;\n\n    if (const auto WL = m_dnd.focusedDevice->getWayland(); WL) {\n        const auto OFFER =\n            m_offers.emplace_back(makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(WL->m_resource->client(), WL->m_resource->version(), 0), m_dnd.currentSource.lock()));\n        if (!OFFER->good()) {\n            WL->m_resource->noMemory();\n            m_offers.pop_back();\n            return;\n        }\n        OFFER->m_source = m_dnd.currentSource;\n        OFFER->m_self   = OFFER;\n        offer           = OFFER;\n    }\n#ifndef NO_XWAYLAND\n    else if (const auto X11 = m_dnd.focusedDevice->getX11(); X11)\n        offer = g_pXWayland->m_wm->createX11DataOffer(g_pSeatManager->m_state.keyboardFocus.lock(), m_dnd.currentSource.lock());\n#endif\n\n    if (!offer) {\n        LOGM(Log::ERR, \"No offer could be created in updateDrag\");\n        return;\n    }\n\n    LOGM(Log::DEBUG, \"New {} dnd offer {:x} for data source {:x}\", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? \"wayland\" : \"X11\", (uintptr_t)offer.get(),\n         (uintptr_t)m_dnd.currentSource.get());\n\n    m_dnd.focusedDevice->sendDataOffer(offer);\n    if (const auto WL = offer->getWayland(); WL)\n        WL->sendData();\n    m_dnd.focusedDevice->sendEnter(wl_display_next_serial(g_pCompositor->m_wlDisplay), g_pSeatManager->m_state.dndPointerFocus.lock(),\n                                   g_pSeatManager->m_state.dndPointerFocus->m_current.size / 2.F, offer);\n}\n\nvoid CWLDataDeviceProtocol::cleanupDndState(bool resetDevice, bool resetSource, bool simulateInput) {\n    m_dnd.dndSurface.reset();\n    m_dnd.dndSurfaceCommit.reset();\n    m_dnd.dndSurfaceDestroy.reset();\n    m_dnd.mouseButton.reset();\n    m_dnd.mouseMove.reset();\n    m_dnd.touchUp.reset();\n    m_dnd.touchMove.reset();\n    m_dnd.tabletTip.reset();\n\n    if (resetDevice)\n        m_dnd.focusedDevice.reset();\n    if (resetSource)\n        m_dnd.currentSource.reset();\n\n    if (simulateInput) {\n        g_pInputManager->simulateMouseMovement();\n        g_pSeatManager->resendEnterEvents();\n    }\n}\n\nvoid CWLDataDeviceProtocol::dropDrag() {\n    if (!m_dnd.focusedDevice || !m_dnd.currentSource) {\n        if (m_dnd.currentSource)\n            abortDrag();\n        return;\n    }\n\n    if (!wasDragSuccessful()) {\n        abortDrag();\n        return;\n    }\n\n    m_dnd.focusedDevice->sendDrop();\n\n#ifndef NO_XWAYLAND\n    if (m_dnd.focusedDevice->getX11()) {\n        m_dnd.focusedDevice->sendLeave();\n        if (m_dnd.overriddenCursor)\n            Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_DND);\n        m_dnd.overriddenCursor = false;\n        cleanupDndState(true, true, true);\n        return;\n    }\n#endif\n\n    m_dnd.focusedDevice->sendLeave();\n    if (m_dnd.overriddenCursor)\n        Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_DND);\n    m_dnd.overriddenCursor = false;\n    cleanupDndState(false, false, false);\n}\n\nbool CWLDataDeviceProtocol::wasDragSuccessful() {\n    if (!m_dnd.currentSource)\n        return false;\n\n    for (auto const& o : m_offers) {\n        if (o->m_dead || o->m_source != m_dnd.currentSource)\n            continue;\n\n        if (o->m_recvd || o->m_accepted)\n            return true;\n    }\n\n#ifndef NO_XWAYLAND\n    if (m_dnd.focusedDevice->getX11())\n        return true;\n#endif\n\n    return false;\n}\n\nvoid CWLDataDeviceProtocol::completeDrag() {\n    if (!m_dnd.focusedDevice && !m_dnd.currentSource)\n        return;\n\n    if (m_dnd.currentSource) {\n        m_dnd.currentSource->sendDndDropPerformed();\n        m_dnd.currentSource->sendDndFinished();\n    }\n\n    cleanupDndState(true, true, true);\n}\n\nvoid CWLDataDeviceProtocol::abortDrag() {\n    cleanupDndState(false, false, false);\n\n    if (m_dnd.overriddenCursor)\n        Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_DND);\n    m_dnd.overriddenCursor = false;\n\n    if (!m_dnd.focusedDevice && !m_dnd.currentSource)\n        return;\n\n    if (m_dnd.focusedDevice) {\n#ifndef NO_XWAYLAND\n        if (auto x11Device = m_dnd.focusedDevice->getX11(); x11Device)\n            x11Device->forceCleanupDnd();\n#endif\n        m_dnd.focusedDevice->sendLeave();\n    }\n\n    if (m_dnd.currentSource)\n        m_dnd.currentSource->cancelled();\n\n    m_dnd.focusedDevice.reset();\n    m_dnd.currentSource.reset();\n\n    g_pInputManager->simulateMouseMovement();\n    g_pSeatManager->resendEnterEvents();\n}\n\nvoid CWLDataDeviceProtocol::renderDND(PHLMONITOR pMonitor, const Time::steady_tp& when) {\n    if (!m_dnd.dndSurface || !m_dnd.dndSurface->m_current.texture)\n        return;\n\n    const auto POS = g_pInputManager->getMouseCoordsInternal();\n\n    Vector2D   surfacePos = POS;\n\n    surfacePos += m_dnd.dndSurface->m_current.offset;\n\n    CBox                         box = CBox{surfacePos, m_dnd.dndSurface->m_current.size}.translate(-pMonitor->m_position).scale(pMonitor->m_scale).round();\n\n    CTexPassElement::SRenderData data;\n    data.tex = m_dnd.dndSurface->m_current.texture;\n    data.box = box;\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n\n    CBox damageBox = CBox{surfacePos, m_dnd.dndSurface->m_current.size}.expand(5);\n    g_pHyprRenderer->damageBox(damageBox);\n\n    m_dnd.dndSurface->frame(when);\n}\n\nbool CWLDataDeviceProtocol::dndActive() {\n    return m_dnd.currentSource;\n}\n\nvoid CWLDataDeviceProtocol::abortDndIfPresent() {\n    if (!dndActive())\n        return;\n    abortDrag();\n}\n"
  },
  {
    "path": "src/protocols/core/DataDevice.hpp",
    "content": "#pragma once\n\n/*\n    Implementations for:\n     - wl_data_offer\n     - wl_data_source\n     - wl_data_device\n     - wl_data_device_manager\n*/\n\n#include <vector>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include <wayland-server-protocol.h>\n#include \"wayland.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n#include \"../types/DataDevice.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CWLDataDeviceResource;\nclass CWLDataDeviceManagerResource;\nclass CWLDataSourceResource;\nclass CWLDataOfferResource;\n\nclass CWLSurfaceResource;\nclass CMonitor;\n\nclass CWLDataOfferResource : public IDataOffer {\n  public:\n    CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_);\n    ~CWLDataOfferResource();\n\n    bool                             good();\n    void                             sendData();\n\n    virtual eDataSourceType          type();\n    virtual SP<CWLDataOfferResource> getWayland();\n    virtual SP<CX11DataOffer>        getX11();\n    virtual SP<IDataSource>          getSource();\n\n    WP<IDataSource>                  m_source;\n    WP<CWLDataOfferResource>         m_self;\n\n    bool                             m_dead     = false;\n    bool                             m_accepted = false;\n    bool                             m_recvd    = false;\n\n  private:\n    SP<CWlDataOffer> m_resource;\n\n    friend class CWLDataDeviceResource;\n};\n\nclass CWLDataSourceResource : public IDataSource {\n  public:\n    CWLDataSourceResource(SP<CWlDataSource> resource_, SP<CWLDataDeviceResource> device_);\n    ~CWLDataSourceResource();\n    static SP<CWLDataSourceResource> fromResource(wl_resource*);\n\n    bool                             good();\n\n    virtual std::vector<std::string> mimes();\n    virtual void                     send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd);\n    virtual void                     accepted(const std::string& mime);\n    virtual void                     cancelled();\n    virtual bool                     hasDnd();\n    virtual bool                     dndDone();\n    virtual void                     error(uint32_t code, const std::string& msg);\n    virtual void                     sendDndFinished();\n    virtual uint32_t                 actions(); // wl_data_device_manager.dnd_action\n    virtual eDataSourceType          type();\n    virtual void                     sendDndDropPerformed();\n    virtual void                     sendDndAction(wl_data_device_manager_dnd_action a);\n\n    bool                             m_used       = false;\n    bool                             m_dnd        = false;\n    bool                             m_dndSuccess = false;\n    bool                             m_dropped    = false;\n\n    WP<CWLDataDeviceResource>        m_device;\n    WP<CWLDataSourceResource>        m_self;\n\n    std::vector<std::string>         m_mimeTypes;\n    uint32_t                         m_supportedActions = 0;\n\n  private:\n    SP<CWlDataSource> m_resource;\n\n    friend class CWLDataDeviceProtocol;\n};\n\nclass CWLDataDeviceResource : public IDataDevice {\n  public:\n    CWLDataDeviceResource(SP<CWlDataDevice> resource_);\n\n    bool                              good();\n    wl_client*                        client();\n\n    virtual SP<CWLDataDeviceResource> getWayland();\n    virtual SP<CX11DataDevice>        getX11();\n    virtual void                      sendDataOffer(SP<IDataOffer> offer);\n    virtual void                      sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer);\n    virtual void                      sendLeave();\n    virtual void                      sendMotion(uint32_t timeMs, const Vector2D& local);\n    virtual void                      sendDrop();\n    virtual void                      sendSelection(SP<IDataOffer> offer);\n    virtual eDataSourceType           type();\n\n    WP<CWLDataDeviceResource>         m_self;\n\n  private:\n    SP<CWlDataDevice>      m_resource;\n    wl_client*             m_client = nullptr;\n\n    WP<CWLSurfaceResource> m_entered;\n\n    friend class CWLDataDeviceProtocol;\n};\n\nclass CWLDataDeviceManagerResource {\n  public:\n    CWLDataDeviceManagerResource(SP<CWlDataDeviceManager> resource_);\n\n    bool                                   good();\n\n    WP<CWLDataDeviceResource>              m_device;\n    std::vector<WP<CWLDataSourceResource>> m_sources;\n\n  private:\n    SP<CWlDataDeviceManager> m_resource;\n};\n\nclass CWLDataDeviceProtocol : public IWaylandProtocol {\n  public:\n    CWLDataDeviceProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    // renders and damages the dnd icon, if present\n    void renderDND(PHLMONITOR pMonitor, const Time::steady_tp& when);\n    // for inputmgr to force refocus\n    // TODO: move handling to seatmgr\n    bool dndActive();\n\n    // called on an escape key pressed, for moments where it gets stuck\n    void abortDndIfPresent();\n\n  private:\n    void destroyResource(CWLDataDeviceManagerResource* resource);\n    void destroyResource(CWLDataDeviceResource* resource);\n    void destroyResource(CWLDataSourceResource* resource);\n    void destroyResource(CWLDataOfferResource* resource);\n\n    //\n    std::vector<SP<CWLDataDeviceManagerResource>> m_managers;\n    std::vector<SP<CWLDataDeviceResource>>        m_devices;\n    std::vector<SP<CWLDataSourceResource>>        m_sources;\n    std::vector<SP<CWLDataOfferResource>>         m_offers;\n\n    //\n\n    void onDestroyDataSource(WP<CWLDataSourceResource> source);\n    void setSelection(SP<IDataSource> source);\n    void sendSelectionToDevice(SP<IDataDevice> dev, SP<IDataSource> sel);\n    void updateSelection();\n    void onKeyboardFocus();\n    void onDndPointerFocus();\n\n    struct {\n        WP<IDataDevice>        focusedDevice;\n        WP<IDataSource>        currentSource;\n        WP<CWLSurfaceResource> dndSurface;\n        WP<CWLSurfaceResource> originSurface;\n        bool                   overriddenCursor = false;\n        CHyprSignalListener    dndSurfaceDestroy;\n        CHyprSignalListener    dndSurfaceCommit;\n\n        // for ending a dnd\n        CHyprSignalListener mouseMove;\n        CHyprSignalListener mouseButton;\n        CHyprSignalListener touchUp;\n        CHyprSignalListener touchMove;\n        CHyprSignalListener tabletTip;\n    } m_dnd;\n\n    void abortDrag();\n    void initiateDrag(WP<CWLDataSourceResource> currentSource, SP<CWLSurfaceResource> dragSurface, SP<CWLSurfaceResource> origin);\n    void updateDrag();\n    void dropDrag();\n    void completeDrag();\n    void cleanupDndState(bool resetDevice, bool resetSource, bool simulateInput);\n    bool wasDragSuccessful();\n\n    //\n    SP<IDataDevice> dataDeviceForClient(wl_client*);\n\n    friend class CSeatManager;\n    friend class CWLDataDeviceManagerResource;\n    friend class CWLDataDeviceResource;\n    friend class CWLDataSourceResource;\n    friend class CWLDataOfferResource;\n\n    struct {\n        CHyprSignalListener onKeyboardFocusChange;\n        CHyprSignalListener onDndPointerFocusChange;\n    } m_listeners;\n};\n\nnamespace PROTO {\n    inline UP<CWLDataDeviceProtocol> data;\n};\n"
  },
  {
    "path": "src/protocols/core/Output.cpp",
    "content": "#include \"Output.hpp\"\n#include \"Compositor.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../helpers/Monitor.hpp\"\n\nCWLOutputResource::CWLOutputResource(SP<CWlOutput> resource_, PHLMONITOR pMonitor) : m_monitor(pMonitor), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_client = m_resource->client();\n\n    if (!m_monitor)\n        return;\n\n    m_resource->setOnDestroy([this](CWlOutput* r) {\n        if (m_monitor && PROTO::outputs.contains(m_monitor->m_name))\n            PROTO::outputs.at(m_monitor->m_name)->destroyResource(this);\n    });\n    m_resource->setRelease([this](CWlOutput* r) {\n        if (m_monitor && PROTO::outputs.contains(m_monitor->m_name))\n            PROTO::outputs.at(m_monitor->m_name)->destroyResource(this);\n    });\n\n    if (m_resource->version() >= 4) {\n        m_resource->sendName(m_monitor->m_name.c_str());\n        m_resource->sendDescription(m_monitor->m_description.c_str());\n    }\n\n    updateState();\n\n    PROTO::compositor->forEachSurface([](SP<CWLSurfaceResource> surf) {\n        auto HLSurf = Desktop::View::CWLSurface::fromResource(surf);\n\n        if (!HLSurf)\n            return;\n\n        const auto GEOMETRY = HLSurf->getSurfaceBoxGlobal();\n\n        if (!GEOMETRY.has_value())\n            return;\n\n        for (auto& m : g_pCompositor->m_monitors) {\n            if (!m->logicalBox().expand(-4).overlaps(*GEOMETRY))\n                continue;\n\n            surf->enter(m);\n        }\n    });\n}\n\nSP<CWLOutputResource> CWLOutputResource::fromResource(wl_resource* res) {\n    auto data = sc<CWLOutputResource*>(sc<CWlOutput*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nbool CWLOutputResource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CWLOutputResource::client() {\n    return m_client;\n}\n\nSP<CWlOutput> CWLOutputResource::getResource() {\n    return m_resource;\n}\n\nvoid CWLOutputResource::updateState() {\n    if (!m_monitor || (m_owner && m_owner->m_defunct))\n        return;\n\n    if (m_resource->version() >= 2)\n        m_resource->sendScale(std::ceil(m_monitor->m_scale));\n\n    m_resource->sendMode(WL_OUTPUT_MODE_CURRENT, m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y, m_monitor->m_refreshRate * 1000.0);\n\n    m_resource->sendGeometry(0, 0, m_monitor->m_output->physicalSize.x, m_monitor->m_output->physicalSize.y, m_monitor->m_output->subpixel, m_monitor->m_output->make.c_str(),\n                             m_monitor->m_output->model.c_str(), m_monitor->m_transform);\n\n    if (m_resource->version() >= 2)\n        m_resource->sendDone();\n}\n\nCWLOutputProtocol::CWLOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name, PHLMONITOR pMonitor) :\n    IWaylandProtocol(iface, ver, name), m_monitor(pMonitor), m_name(pMonitor->m_name) {\n\n    m_listeners.modeChanged = m_monitor->m_events.modeChanged.listen([this] {\n        for (auto const& o : m_outputs) {\n            o->updateState();\n        }\n    });\n}\n\nvoid CWLOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    if UNLIKELY (m_defunct)\n        Log::logger->log(Log::WARN, \"[wl_output] Binding a wl_output that's inert?? Possible client bug.\");\n\n    const auto RESOURCE = m_outputs.emplace_back(makeShared<CWLOutputResource>(makeShared<CWlOutput>(client, ver, id), m_monitor.lock()));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_outputs.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self  = RESOURCE;\n    RESOURCE->m_owner = m_self;\n    m_events.outputBound.emit(RESOURCE);\n}\n\nvoid CWLOutputProtocol::destroyResource(CWLOutputResource* resource) {\n    std::erase_if(m_outputs, [&](const auto& other) { return other.get() == resource; });\n\n    if (m_outputs.empty() && m_defunct)\n        PROTO::outputs.erase(m_name);\n}\n\nstd::vector<SP<CWLOutputResource>> CWLOutputProtocol::outputResourcesFrom(wl_client* client) {\n    std::vector<SP<CWLOutputResource>> ret;\n\n    for (auto const& r : m_outputs) {\n        if (r->client() != client)\n            continue;\n\n        ret.emplace_back(r);\n    }\n\n    return ret;\n}\n\nvoid CWLOutputProtocol::remove() {\n    if UNLIKELY (m_defunct)\n        return;\n\n    m_defunct = true;\n    removeGlobal();\n}\n\nbool CWLOutputProtocol::isDefunct() {\n    return m_defunct;\n}\n\nvoid CWLOutputProtocol::sendDone() {\n    if UNLIKELY (m_defunct)\n        return;\n\n    for (auto const& r : m_outputs) {\n        r->m_resource->sendDone();\n    }\n}\n"
  },
  {
    "path": "src/protocols/core/Output.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include \"wayland.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n\nclass CMonitor;\nclass CWLOutputProtocol;\n\nclass CWLOutputResource {\n  public:\n    CWLOutputResource(SP<CWlOutput> resource_, PHLMONITOR pMonitor);\n    static SP<CWLOutputResource> fromResource(wl_resource*);\n\n    bool                         good();\n    wl_client*                   client();\n    SP<CWlOutput>                getResource();\n    void                         updateState();\n\n    PHLMONITORREF                m_monitor;\n    WP<CWLOutputProtocol>        m_owner;\n    WP<CWLOutputResource>        m_self;\n\n  private:\n    SP<CWlOutput> m_resource;\n    wl_client*    m_client = nullptr;\n\n    friend class CWLOutputProtocol;\n};\n\nclass CWLOutputProtocol : public IWaylandProtocol {\n  public:\n    CWLOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name, PHLMONITOR pMonitor);\n\n    virtual void                       bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    std::vector<SP<CWLOutputResource>> outputResourcesFrom(wl_client* client);\n    void                               sendDone();\n\n    PHLMONITORREF                      m_monitor;\n    WP<CWLOutputProtocol>              m_self;\n\n    // will mark the protocol for removal, will be removed when no. of bound outputs is 0 (or when overwritten by a new global)\n    void remove();\n    bool isDefunct(); // true if above was called\n\n    struct {\n        CSignalT<SP<CWLOutputResource>> outputBound;\n    } m_events;\n\n  private:\n    void destroyResource(CWLOutputResource* resource);\n\n    //\n    std::vector<SP<CWLOutputResource>> m_outputs;\n    bool                               m_defunct = false;\n    std::string                        m_name    = \"\";\n\n    struct {\n        CHyprSignalListener modeChanged;\n    } m_listeners;\n\n    friend class CWLOutputResource;\n};\n\nnamespace PROTO {\n    inline std::unordered_map<std::string, SP<CWLOutputProtocol>> outputs;\n};\n"
  },
  {
    "path": "src/protocols/core/Seat.cpp",
    "content": "#include \"Seat.hpp\"\n#include \"Compositor.hpp\"\n#include \"DataDevice.hpp\"\n#include \"../../devices/IKeyboard.hpp\"\n#include \"../../devices/IHID.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include <algorithm>\n\n#include <fcntl.h>\n\nconstexpr const float WL_FIXED_EPSILON = 1.F / 256.F;\n\nCWLTouchResource::CWLTouchResource(SP<CWlTouch> resource_, SP<CWLSeatResource> owner_) : m_owner(owner_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](CWlTouch* r) { PROTO::seat->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlTouch* r) { PROTO::seat->destroyResource(this); });\n}\n\nbool CWLTouchResource::good() {\n    return m_resource->resource();\n}\n\nvoid CWLTouchResource::sendDown(SP<CWLSurfaceResource> surface, uint32_t timeMs, int32_t id, const Vector2D& local) {\n    if (!m_owner || !surface || !surface->getResource()->resource())\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    ASSERT(surface->client() == m_owner->client());\n\n    m_currentSurface           = surface;\n    m_listeners.destroySurface = surface->m_events.destroy.listen([this, timeMs, id] { sendUp(timeMs + 10 /* hack */, id); });\n\n    m_resource->sendDown(g_pSeatManager->nextSerial(m_owner.lock()), timeMs, surface->getResource().get(), id, wl_fixed_from_double(local.x), wl_fixed_from_double(local.y));\n\n    m_fingers++;\n}\n\nvoid CWLTouchResource::sendUp(uint32_t timeMs, int32_t id) {\n    if (!m_owner)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    m_resource->sendUp(g_pSeatManager->nextSerial(m_owner.lock()), timeMs, id);\n    m_fingers--;\n    if (m_fingers <= 0) {\n        m_currentSurface.reset();\n        m_listeners.destroySurface.reset();\n        m_fingers = 0;\n    }\n}\n\nvoid CWLTouchResource::sendMotion(uint32_t timeMs, int32_t id, const Vector2D& local) {\n    if (!m_owner)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    m_resource->sendMotion(timeMs, id, wl_fixed_from_double(local.x), wl_fixed_from_double(local.y));\n}\n\nvoid CWLTouchResource::sendFrame() {\n    if (!m_owner)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    m_resource->sendFrame();\n}\n\nvoid CWLTouchResource::sendCancel() {\n    if (!m_owner || !m_currentSurface)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    m_resource->sendCancel();\n}\n\nvoid CWLTouchResource::sendShape(int32_t id, const Vector2D& shape) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 6)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    m_resource->sendShape(id, wl_fixed_from_double(shape.x), wl_fixed_from_double(shape.y));\n}\n\nvoid CWLTouchResource::sendOrientation(int32_t id, double angle) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 6)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH))\n        return;\n\n    m_resource->sendOrientation(id, wl_fixed_from_double(angle));\n}\n\nCWLPointerResource::CWLPointerResource(SP<CWlPointer> resource_, SP<CWLSeatResource> owner_) : m_owner(owner_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setData(this);\n\n    m_resource->setRelease([this](CWlPointer* r) { PROTO::seat->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlPointer* r) { PROTO::seat->destroyResource(this); });\n\n    m_resource->setSetCursor([this](CWlPointer* r, uint32_t serial, wl_resource* surf, int32_t hotX, int32_t hotY) {\n        if (!m_owner) {\n            LOGM(Log::ERR, \"Client bug: setCursor when seatClient is already dead\");\n            return;\n        }\n\n        auto surfResource = surf ? CWLSurfaceResource::fromResource(surf) : nullptr;\n\n        if (surfResource && surfResource->m_role->role() != SURFACE_ROLE_CURSOR && surfResource->m_role->role() != SURFACE_ROLE_UNASSIGNED) {\n            r->error(-1, \"Cursor surface already has a different role\");\n            return;\n        }\n\n        if (surfResource && surfResource->m_role->role() != SURFACE_ROLE_CURSOR) {\n            surfResource->m_role = makeShared<CCursorSurfaceRole>();\n            surfResource->updateCursorShm();\n        }\n\n        g_pSeatManager->onSetCursor(m_owner.lock(), serial, surfResource, {hotX, hotY});\n    });\n\n    if (g_pSeatManager->m_state.pointerFocus && g_pSeatManager->m_state.pointerFocus->client() == m_resource->client())\n        sendEnter(g_pSeatManager->m_state.pointerFocus.lock(), {-1, -1} /* Coords don't really matter that much, they will be updated next move */);\n}\n\nCWLPointerResource::~CWLPointerResource() {\n    m_events.destroyed.emit();\n}\n\nint CWLPointerResource::version() {\n    return m_resource->version();\n}\n\nbool CWLPointerResource::good() {\n    return m_resource->resource();\n}\n\nSP<CWLPointerResource> CWLPointerResource::fromResource(wl_resource* res) {\n    auto data = sc<CWLPointerResource*>(sc<CWlPointer*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nvoid CWLPointerResource::sendEnter(SP<CWLSurfaceResource> surface, const Vector2D& local) {\n    if (!m_owner || m_currentSurface == surface || !surface->getResource()->resource())\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    if (m_currentSurface) {\n        LOGM(Log::WARN, \"requested CWLPointerResource::sendEnter without sendLeave first.\");\n        sendLeave();\n    }\n\n    ASSERT(surface->client() == m_owner->client());\n\n    m_currentSurface           = surface;\n    m_listeners.destroySurface = surface->m_events.destroy.listen([this] { sendLeave(); });\n\n    const auto fixedLocal = fixPosWithWlFixed(local);\n\n    m_resource->sendEnter(g_pSeatManager->nextSerial(m_owner.lock()), surface->getResource().get(), wl_fixed_from_double(fixedLocal.x), wl_fixed_from_double(fixedLocal.y));\n}\n\nvoid CWLPointerResource::sendLeave() {\n    if (!m_owner || !m_currentSurface || !m_currentSurface->getResource()->resource())\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    // release all buttons unless we have a dnd going on in which case\n    // the events shall be lost.\n    if (!PROTO::data->dndActive()) {\n        for (auto const& b : m_pressedButtons) {\n            sendButton(Time::millis(Time::steadyNow()), b, WL_POINTER_BUTTON_STATE_RELEASED);\n        }\n    }\n\n    m_pressedButtons.clear();\n\n    m_resource->sendLeave(g_pSeatManager->nextSerial(m_owner.lock()), m_currentSurface->getResource().get());\n    m_currentSurface.reset();\n    m_listeners.destroySurface.reset();\n}\n\nvoid CWLPointerResource::sendMotion(uint32_t timeMs, const Vector2D& local) {\n    if (!m_owner || !m_currentSurface)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    const auto fixedLocal = fixPosWithWlFixed(local);\n\n    m_resource->sendMotion(timeMs, wl_fixed_from_double(fixedLocal.x), wl_fixed_from_double(fixedLocal.y));\n}\n\nvoid CWLPointerResource::sendButton(uint32_t timeMs, uint32_t button, wl_pointer_button_state state) {\n    if (!m_owner || !m_currentSurface)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    if (state == WL_POINTER_BUTTON_STATE_RELEASED && std::ranges::find(m_pressedButtons, button) == m_pressedButtons.end()) {\n        LOGM(Log::ERR, \"sendButton release on a non-pressed button\");\n        return;\n    } else if (state == WL_POINTER_BUTTON_STATE_PRESSED && std::ranges::find(m_pressedButtons, button) != m_pressedButtons.end()) {\n        LOGM(Log::ERR, \"sendButton press on a non-pressed button\");\n        return;\n    }\n\n    if (state == WL_POINTER_BUTTON_STATE_RELEASED)\n        std::erase(m_pressedButtons, button);\n    else if (state == WL_POINTER_BUTTON_STATE_PRESSED)\n        m_pressedButtons.emplace_back(button);\n\n    m_resource->sendButton(g_pSeatManager->nextSerial(m_owner.lock()), timeMs, button, state);\n}\n\nvoid CWLPointerResource::sendAxis(uint32_t timeMs, wl_pointer_axis axis, double value) {\n    if (!m_owner || !m_currentSurface)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendAxis(timeMs, axis, wl_fixed_from_double(value));\n}\n\nvoid CWLPointerResource::sendFrame() {\n    if (!m_owner || m_resource->version() < 5)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendFrame();\n}\n\nvoid CWLPointerResource::sendAxisSource(wl_pointer_axis_source source) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 5)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendAxisSource(source);\n}\n\nvoid CWLPointerResource::sendAxisStop(uint32_t timeMs, wl_pointer_axis axis) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 5)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendAxisStop(timeMs, axis);\n}\n\nvoid CWLPointerResource::sendAxisDiscrete(wl_pointer_axis axis, int32_t discrete) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 5)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendAxisDiscrete(axis, discrete);\n}\n\nvoid CWLPointerResource::sendAxisValue120(wl_pointer_axis axis, int32_t value120) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 8)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendAxisValue120(axis, value120);\n}\n\nvoid CWLPointerResource::sendAxisRelativeDirection(wl_pointer_axis axis, wl_pointer_axis_relative_direction direction) {\n    if (!m_owner || !m_currentSurface || m_resource->version() < 9)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER))\n        return;\n\n    m_resource->sendAxisRelativeDirection(axis, direction);\n}\n\nVector2D CWLPointerResource::fixPosWithWlFixed(const Vector2D& pos) {\n    if (!m_currentSurface)\n        return pos;\n\n    Vector2D newPos = pos;\n\n    // When our cursor pos is right at the edge, wl_fixed will round it up,\n    // instead of down, meaning 10.999999 -> 11 instead of 10 + 255/256\n    // if we are within that epsilon, move the coord a bit down to account for that.\n    if (std::abs(newPos.x - m_currentSurface->m_current.size.x) < WL_FIXED_EPSILON)\n        newPos.x = m_currentSurface->m_current.size.x - WL_FIXED_EPSILON * 2;\n    if (std::abs(newPos.y - m_currentSurface->m_current.size.y) < WL_FIXED_EPSILON)\n        newPos.y = m_currentSurface->m_current.size.y - WL_FIXED_EPSILON * 2;\n\n    return newPos;\n}\n\nCWLKeyboardResource::CWLKeyboardResource(SP<CWlKeyboard> resource_, SP<CWLSeatResource> owner_) : m_owner(owner_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](CWlKeyboard* r) { PROTO::seat->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlKeyboard* r) { PROTO::seat->destroyResource(this); });\n\n    if (!g_pSeatManager->m_keyboard) {\n        LOGM(Log::ERR, \"No keyboard on bound wl_keyboard??\");\n        return;\n    }\n\n    sendKeymap(g_pSeatManager->m_keyboard.lock());\n    repeatInfo(g_pSeatManager->m_keyboard->m_repeatRate, g_pSeatManager->m_keyboard->m_repeatDelay);\n\n    if (g_pSeatManager->m_state.keyboardFocus && g_pSeatManager->m_state.keyboardFocus->client() == m_resource->client()) {\n        wl_array keys;\n        wl_array_init(&keys);\n\n        sendEnter(g_pSeatManager->m_state.keyboardFocus.lock(), &keys);\n\n        wl_array_release(&keys);\n    }\n}\n\nbool CWLKeyboardResource::good() {\n    return m_resource->resource();\n}\n\nvoid CWLKeyboardResource::sendKeymap(SP<IKeyboard> keyboard) {\n    if (!keyboard)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    std::string_view                keymap = keyboard->m_xkbKeymapV1String;\n    Hyprutils::OS::CFileDescriptor& fd     = keyboard->m_xkbKeymapV1FD;\n    uint32_t                        size   = keyboard->m_xkbKeymapV1String.length() + 1;\n\n    if (keymap == m_lastKeymap)\n        return;\n\n    m_lastKeymap = keymap;\n\n    const wl_keyboard_keymap_format format = keyboard ? WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 : WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP;\n\n    m_resource->sendKeymap(format, fd.get(), size);\n}\n\nvoid CWLKeyboardResource::sendEnter(SP<CWLSurfaceResource> surface, wl_array* keys) {\n    ASSERT(keys);\n\n    if (!m_owner || m_currentSurface == surface || !surface->getResource()->resource())\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    if (m_currentSurface) {\n        LOGM(Log::WARN, \"requested CWLKeyboardResource::sendEnter without sendLeave first.\");\n        sendLeave();\n    }\n\n    ASSERT(surface->client() == m_owner->client());\n\n    m_currentSurface           = surface;\n    m_listeners.destroySurface = surface->m_events.destroy.listen([this] { sendLeave(); });\n\n    m_resource->sendEnter(g_pSeatManager->nextSerial(m_owner.lock()), surface->getResource().get(), keys);\n}\n\nvoid CWLKeyboardResource::sendLeave() {\n    if (!m_owner || !m_currentSurface || !m_currentSurface->getResource()->resource())\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    m_resource->sendLeave(g_pSeatManager->nextSerial(m_owner.lock()), m_currentSurface->getResource().get());\n    m_currentSurface.reset();\n    m_listeners.destroySurface.reset();\n}\n\nvoid CWLKeyboardResource::sendKey(uint32_t timeMs, uint32_t key, wl_keyboard_key_state state) {\n    if (!m_owner || !m_currentSurface)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    m_resource->sendKey(g_pSeatManager->nextSerial(m_owner.lock()), timeMs, key, state);\n}\n\nvoid CWLKeyboardResource::sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {\n    if (!m_owner || !m_currentSurface)\n        return;\n\n    if (!(PROTO::seat->m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    m_resource->sendModifiers(g_pSeatManager->nextSerial(m_owner.lock()), depressed, latched, locked, group);\n}\n\nvoid CWLKeyboardResource::repeatInfo(uint32_t rate, uint32_t delayMs) {\n    if (!m_owner || m_resource->version() < 4 || (rate == m_lastRate && delayMs == m_lastDelayMs))\n        return;\n    m_lastRate    = rate;\n    m_lastDelayMs = delayMs;\n\n    m_resource->sendRepeatInfo(rate, delayMs);\n}\n\nCWLSeatResource::CWLSeatResource(SP<CWlSeat> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlSeat* r) {\n        m_events.destroy.emit();\n        PROTO::seat->destroyResource(this);\n    });\n    m_resource->setRelease([this](CWlSeat* r) {\n        m_events.destroy.emit();\n        PROTO::seat->destroyResource(this);\n    });\n\n    m_client = m_resource->client();\n\n    m_resource->setGetKeyboard([this](CWlSeat* r, uint32_t id) {\n        const auto RESOURCE = PROTO::seat->m_keyboards.emplace_back(makeShared<CWLKeyboardResource>(makeShared<CWlKeyboard>(r->client(), r->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::seat->m_keyboards.pop_back();\n            return;\n        }\n\n        m_keyboards.emplace_back(RESOURCE);\n    });\n\n    m_resource->setGetPointer([this](CWlSeat* r, uint32_t id) {\n        const auto RESOURCE = PROTO::seat->m_pointers.emplace_back(makeShared<CWLPointerResource>(makeShared<CWlPointer>(r->client(), r->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::seat->m_pointers.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n\n        m_pointers.emplace_back(RESOURCE);\n    });\n\n    m_resource->setGetTouch([this](CWlSeat* r, uint32_t id) {\n        const auto RESOURCE = PROTO::seat->m_touches.emplace_back(makeShared<CWLTouchResource>(makeShared<CWlTouch>(r->client(), r->version(), id), m_self.lock()));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::seat->m_touches.pop_back();\n            return;\n        }\n\n        m_touches.emplace_back(RESOURCE);\n    });\n\n    if (m_resource->version() >= 2)\n        m_resource->sendName(HL_SEAT_NAME);\n\n    sendCapabilities(PROTO::seat->m_currentCaps);\n}\n\nCWLSeatResource::~CWLSeatResource() {\n    m_events.destroy.emit();\n}\n\nvoid CWLSeatResource::sendCapabilities(uint32_t caps) {\n    uint32_t wlCaps = 0;\n    if (caps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD)\n        wlCaps |= WL_SEAT_CAPABILITY_KEYBOARD;\n    if (caps & eHIDCapabilityType::HID_INPUT_CAPABILITY_POINTER)\n        wlCaps |= WL_SEAT_CAPABILITY_POINTER;\n    if (caps & eHIDCapabilityType::HID_INPUT_CAPABILITY_TOUCH)\n        wlCaps |= WL_SEAT_CAPABILITY_TOUCH;\n\n    m_resource->sendCapabilities(sc<wl_seat_capability>(wlCaps));\n}\n\nbool CWLSeatResource::good() {\n    return m_resource->resource();\n}\n\nwl_client* CWLSeatResource::client() {\n    return m_client;\n}\n\nCWLSeatProtocol::CWLSeatProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CWLSeatProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_seatResources.emplace_back(makeShared<CWLSeatResource>(makeShared<CWlSeat>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_seatResources.pop_back();\n        return;\n    }\n\n    RESOURCE->m_self = RESOURCE;\n\n    LOGM(Log::DEBUG, \"New seat resource bound at {:x}\", (uintptr_t)RESOURCE.get());\n\n    m_events.newSeatResource.emit(RESOURCE);\n}\n\nvoid CWLSeatProtocol::destroyResource(CWLSeatResource* seat) {\n    std::erase_if(m_seatResources, [&](const auto& other) { return other.get() == seat; });\n}\n\nvoid CWLSeatProtocol::destroyResource(CWLKeyboardResource* resource) {\n    std::erase_if(m_keyboards, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLSeatProtocol::destroyResource(CWLPointerResource* resource) {\n    std::erase_if(m_pointers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLSeatProtocol::destroyResource(CWLTouchResource* resource) {\n    std::erase_if(m_touches, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLSeatProtocol::updateCapabilities(uint32_t caps) {\n    if (caps == m_currentCaps)\n        return;\n\n    m_currentCaps = caps;\n\n    for (auto const& s : m_seatResources) {\n        s->sendCapabilities(caps);\n    }\n}\n\nvoid CWLSeatProtocol::updateKeymap() {\n    if (!(m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    for (auto const& k : m_keyboards) {\n        k->sendKeymap(g_pSeatManager->m_keyboard.lock());\n    }\n}\n\nvoid CWLSeatProtocol::updateRepeatInfo(uint32_t rate, uint32_t delayMs) {\n    if (!(m_currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD))\n        return;\n\n    for (auto const& k : m_keyboards) {\n        k->repeatInfo(rate, delayMs);\n    }\n}\n\nSP<CWLSeatResource> CWLSeatProtocol::seatResourceForClient(wl_client* client) {\n    for (auto const& r : m_seatResources) {\n        if (r->client() == client)\n            return r;\n    }\n\n    return nullptr;\n}\n\nstd::vector<uint8_t>& CCursorSurfaceRole::cursorPixelData(SP<CWLSurfaceResource> surface) {\n    RASSERT(surface->m_role->role() == SURFACE_ROLE_CURSOR, \"cursorPixelData called on a non-cursor surface\");\n\n    auto role = sc<CCursorSurfaceRole*>(surface->m_role.get());\n    return role->m_cursorShmPixelData;\n}\n"
  },
  {
    "path": "src/protocols/core/Seat.hpp",
    "content": "#pragma once\n\n/*\n    Implementations for:\n     - wl_seat\n     - wl_keyboard\n     - wl_pointer\n     - wl_touch\n*/\n\n#include <vector>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include <wayland-server-protocol.h>\n#include <wayland-util.h>\n#include \"wayland.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include \"../types/SurfaceRole.hpp\"\n\nconstexpr const char* HL_SEAT_NAME = \"Hyprland\";\n\nclass IKeyboard;\nclass CWLSurfaceResource;\n\nclass CWLPointerResource;\nclass CWLKeyboardResource;\nclass CWLTouchResource;\nclass CWLSeatResource;\n\nclass CCursorSurfaceRole : public ISurfaceRole {\n  public:\n    virtual eSurfaceRole role() {\n        return SURFACE_ROLE_CURSOR;\n    }\n\n    // gets the current pixel data from a shm surface\n    // will assert if the surface is not a cursor\n    static std::vector<uint8_t>& cursorPixelData(SP<CWLSurfaceResource> surface);\n\n  private:\n    std::vector<uint8_t> m_cursorShmPixelData;\n};\n\nclass CWLTouchResource {\n  public:\n    CWLTouchResource(SP<CWlTouch> resource_, SP<CWLSeatResource> owner_);\n\n    bool                good();\n    void                sendDown(SP<CWLSurfaceResource> surface, uint32_t timeMs, int32_t id, const Vector2D& local);\n    void                sendUp(uint32_t timeMs, int32_t id);\n    void                sendMotion(uint32_t timeMs, int32_t id, const Vector2D& local);\n    void                sendFrame();\n    void                sendCancel();\n    void                sendShape(int32_t id, const Vector2D& shape);\n    void                sendOrientation(int32_t id, double angle);\n\n    WP<CWLSeatResource> m_owner;\n\n  private:\n    SP<CWlTouch>           m_resource;\n    WP<CWLSurfaceResource> m_currentSurface;\n\n    int                    m_fingers = 0;\n\n    struct {\n        CHyprSignalListener destroySurface;\n    } m_listeners;\n};\n\nclass CWLPointerResource {\n  public:\n    CWLPointerResource(SP<CWlPointer> resource_, SP<CWLSeatResource> owner_);\n    ~CWLPointerResource();\n\n    bool                good();\n    int                 version();\n    void                sendEnter(SP<CWLSurfaceResource> surface, const Vector2D& local);\n    void                sendLeave();\n    void                sendMotion(uint32_t timeMs, const Vector2D& local);\n    void                sendButton(uint32_t timeMs, uint32_t button, wl_pointer_button_state state);\n    void                sendAxis(uint32_t timeMs, wl_pointer_axis axis, double value);\n    void                sendFrame();\n    void                sendAxisSource(wl_pointer_axis_source source);\n    void                sendAxisStop(uint32_t timeMs, wl_pointer_axis axis);\n    void                sendAxisDiscrete(wl_pointer_axis axis, int32_t discrete);\n    void                sendAxisValue120(wl_pointer_axis axis, int32_t value120);\n    void                sendAxisRelativeDirection(wl_pointer_axis axis, wl_pointer_axis_relative_direction direction);\n\n    WP<CWLSeatResource> m_owner;\n\n    struct {\n        CSignalT<> destroyed;\n    } m_events;\n\n    //\n    static SP<CWLPointerResource> fromResource(wl_resource* res);\n\n  private:\n    SP<CWlPointer>         m_resource;\n    WP<CWLSurfaceResource> m_currentSurface;\n    WP<CWLPointerResource> m_self;\n\n    std::vector<uint32_t>  m_pressedButtons;\n\n    Vector2D               fixPosWithWlFixed(const Vector2D& pos);\n\n    struct {\n        CHyprSignalListener destroySurface;\n    } m_listeners;\n\n    friend class CWLSeatResource;\n};\n\nclass CWLKeyboardResource {\n  public:\n    CWLKeyboardResource(SP<CWlKeyboard> resource_, SP<CWLSeatResource> owner_);\n\n    bool                good();\n    void                sendKeymap(SP<IKeyboard> keeb);\n    void                sendEnter(SP<CWLSurfaceResource> surface, wl_array* keys);\n    void                sendLeave();\n    void                sendKey(uint32_t timeMs, uint32_t key, wl_keyboard_key_state state);\n    void                sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group);\n    void                repeatInfo(uint32_t rate, uint32_t delayMs);\n\n    WP<CWLSeatResource> m_owner;\n\n  private:\n    SP<CWlKeyboard>        m_resource;\n    WP<CWLSurfaceResource> m_currentSurface;\n\n    struct {\n        CHyprSignalListener destroySurface;\n    } m_listeners;\n\n    std::string m_lastKeymap  = \"<none>\";\n    uint32_t    m_lastRate    = 0;\n    uint32_t    m_lastDelayMs = 0;\n};\n\nclass CWLSeatResource {\n  public:\n    CWLSeatResource(SP<CWlSeat> resource_);\n    ~CWLSeatResource();\n\n    void                                 sendCapabilities(uint32_t caps); // uses IHID capabilities\n\n    bool                                 good();\n    wl_client*                           client();\n\n    std::vector<WP<CWLPointerResource>>  m_pointers;\n    std::vector<WP<CWLKeyboardResource>> m_keyboards;\n    std::vector<WP<CWLTouchResource>>    m_touches;\n\n    WP<CWLSeatResource>                  m_self;\n\n    struct {\n        CSignalT<> destroy;\n    } m_events;\n\n  private:\n    SP<CWlSeat> m_resource;\n    wl_client*  m_client = nullptr;\n};\n\nclass CWLSeatProtocol : public IWaylandProtocol {\n  public:\n    CWLSeatProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n    struct {\n        CSignalT<SP<CWLSeatResource>> newSeatResource;\n    } m_events;\n\n  private:\n    void updateCapabilities(uint32_t caps); // in IHID caps\n    void updateRepeatInfo(uint32_t rate, uint32_t delayMs);\n    void updateKeymap();\n\n    void destroyResource(CWLSeatResource* resource);\n    void destroyResource(CWLKeyboardResource* resource);\n    void destroyResource(CWLTouchResource* resource);\n    void destroyResource(CWLPointerResource* resource);\n\n    //\n    std::vector<SP<CWLSeatResource>>     m_seatResources;\n    std::vector<SP<CWLKeyboardResource>> m_keyboards;\n    std::vector<SP<CWLTouchResource>>    m_touches;\n    std::vector<SP<CWLPointerResource>>  m_pointers;\n\n    SP<CWLSeatResource>                  seatResourceForClient(wl_client* client);\n\n    //\n    uint32_t m_currentCaps = 0;\n\n    friend class CWLSeatResource;\n    friend class CWLKeyboardResource;\n    friend class CWLTouchResource;\n    friend class CWLPointerResource;\n    friend class CSeatManager;\n};\n\nnamespace PROTO {\n    inline UP<CWLSeatProtocol> seat;\n};\n"
  },
  {
    "path": "src/protocols/core/Shm.cpp",
    "content": "#include \"Shm.hpp\"\n#include <algorithm>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <drm_fourcc.h>\n#include \"../../render/Texture.hpp\"\n#include \"../types/WLBuffer.hpp\"\n#include \"../../helpers/Format.hpp\"\n#include \"../../render/Renderer.hpp\"\nusing namespace Hyprutils::OS;\n\nCWLSHMBuffer::CWLSHMBuffer(WP<CWLSHMPoolResource> pool_, uint32_t id, int32_t offset_, const Vector2D& size_, int32_t stride_, uint32_t fmt_) {\n    if UNLIKELY (!pool_)\n        return;\n\n    if UNLIKELY (!pool_->m_pool->m_data)\n        return;\n\n    size     = size_;\n    m_pool   = pool_->m_pool;\n    m_stride = stride_;\n    m_fmt    = fmt_;\n    m_offset = offset_;\n    m_opaque = NFormatUtils::isFormatOpaque(NFormatUtils::shmToDRM(fmt_));\n\n    m_resource = CWLBufferResource::create(makeShared<CWlBuffer>(pool_->m_resource->client(), 1, id));\n\n    m_listeners.bufferResourceDestroy = events.destroy.listen([this] {\n        m_listeners.bufferResourceDestroy.reset();\n        PROTO::shm->destroyResource(this);\n    });\n}\n\nCWLSHMBuffer::~CWLSHMBuffer() {\n    if (m_resource)\n        m_resource->sendRelease();\n}\n\nAquamarine::eBufferCapability CWLSHMBuffer::caps() {\n    return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;\n}\n\nAquamarine::eBufferType CWLSHMBuffer::type() {\n    return Aquamarine::eBufferType::BUFFER_TYPE_SHM;\n}\n\nbool CWLSHMBuffer::isSynchronous() {\n    return true;\n}\n\nAquamarine::SSHMAttrs CWLSHMBuffer::shm() {\n    Aquamarine::SSHMAttrs attrs;\n    attrs.success = true;\n    attrs.fd      = m_pool->m_fd.get();\n    attrs.format  = NFormatUtils::shmToDRM(m_fmt);\n    attrs.size    = size;\n    attrs.stride  = m_stride;\n    attrs.offset  = m_offset;\n    return attrs;\n}\n\nstd::tuple<uint8_t*, uint32_t, size_t> CWLSHMBuffer::beginDataPtr(uint32_t flags) {\n    return {sc<uint8_t*>(m_pool->m_data) + m_offset, m_fmt, m_stride * size.y};\n}\n\nvoid CWLSHMBuffer::endDataPtr() {\n    ;\n}\n\nbool CWLSHMBuffer::good() {\n    return true;\n}\n\nvoid CWLSHMBuffer::update(const CRegion& damage) {\n    ;\n}\n\nCSHMPool::CSHMPool(CFileDescriptor fd_, size_t size_) : m_fd(std::move(fd_)), m_size(size_), m_data(mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd.get(), 0)) {\n    ;\n}\n\nCSHMPool::~CSHMPool() {\n    if (m_data != MAP_FAILED)\n        munmap(m_data, m_size);\n}\n\nvoid CSHMPool::resize(size_t size_) {\n    LOGM(Log::DEBUG, \"Resizing a SHM pool from {} to {}\", m_size, size_);\n\n    if (m_data != MAP_FAILED)\n        munmap(m_data, m_size);\n\n    m_size = size_;\n    m_data = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd.get(), 0);\n\n    if UNLIKELY (m_data == MAP_FAILED)\n        LOGM(Log::ERR, \"Couldn't mmap {} bytes from fd {} of shm client\", m_size, m_fd.get());\n}\n\nstatic int shmIsSizeValid(CFileDescriptor& fd, size_t size) {\n    struct stat st;\n    if UNLIKELY (fstat(fd.get(), &st) == -1) {\n        LOGM(Log::ERR, \"Couldn't get stat for fd {} of shm client\", fd.get());\n        return 0;\n    }\n\n    return sc<size_t>(st.st_size) >= size;\n}\n\nCWLSHMPoolResource::CWLSHMPoolResource(UP<CWlShmPool>&& resource_, CFileDescriptor fd_, size_t size_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    if UNLIKELY (!shmIsSizeValid(fd_, size_)) {\n        m_resource->error(-1, \"The size of the file is not big enough for the shm pool\");\n        return;\n    }\n\n    m_pool = makeShared<CSHMPool>(std::move(fd_), size_);\n\n    m_resource->setDestroy([this](CWlShmPool* r) { PROTO::shm->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlShmPool* r) { PROTO::shm->destroyResource(this); });\n\n    m_resource->setResize([this](CWlShmPool* r, int32_t size_) {\n        if UNLIKELY (size_ < sc<int32_t>(m_pool->m_size)) {\n            r->error(-1, \"Shrinking a shm pool is illegal\");\n            return;\n        }\n        if UNLIKELY (!shmIsSizeValid(m_pool->m_fd, size_)) {\n            r->error(-1, \"The size of the file is not big enough for the shm pool\");\n            return;\n        }\n\n        m_pool->resize(size_);\n    });\n\n    m_resource->setCreateBuffer([this](CWlShmPool* r, uint32_t id, int32_t offset, int32_t w, int32_t h, int32_t stride, uint32_t fmt) {\n        if UNLIKELY (!m_pool || !m_pool->m_data) {\n            r->error(-1, \"The provided shm pool failed to allocate properly\");\n            return;\n        }\n\n        if UNLIKELY (std::ranges::find(PROTO::shm->m_shmFormats, fmt) == PROTO::shm->m_shmFormats.end()) {\n            r->error(WL_SHM_ERROR_INVALID_FORMAT, \"Format invalid\");\n            return;\n        }\n\n        if UNLIKELY (offset < 0 || w <= 0 || h <= 0 || stride <= 0) {\n            r->error(WL_SHM_ERROR_INVALID_STRIDE, \"Invalid stride, w, h, or offset\");\n            return;\n        }\n\n        const auto& RESOURCE = PROTO::shm->m_buffers.emplace_back(makeShared<CWLSHMBuffer>(m_self, id, offset, Vector2D{w, h}, stride, fmt));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::shm->m_buffers.pop_back();\n            return;\n        }\n\n        // append instance so that buffer knows its owner\n        RESOURCE->m_resource->m_buffer = RESOURCE;\n    });\n\n    if UNLIKELY (m_pool->m_data == MAP_FAILED)\n        m_resource->error(WL_SHM_ERROR_INVALID_FD, \"Couldn't mmap from fd\");\n}\n\nbool CWLSHMPoolResource::good() {\n    return m_resource->resource();\n}\n\nCWLSHMResource::CWLSHMResource(UP<CWlShm>&& resource_) : m_resource(std::move(resource_)) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setRelease([this](CWlShm* r) { PROTO::shm->destroyResource(this); });\n    m_resource->setOnDestroy([this](CWlShm* r) { PROTO::shm->destroyResource(this); });\n\n    m_resource->setCreatePool([](CWlShm* r, uint32_t id, int32_t fd, int32_t size) {\n        CFileDescriptor poolFd{fd};\n        const auto&     RESOURCE = PROTO::shm->m_pools.emplace_back(makeUnique<CWLSHMPoolResource>(makeUnique<CWlShmPool>(r->client(), r->version(), id), std::move(poolFd), size));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::shm->m_pools.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n    });\n\n    // send a few supported formats. No need for any other I think?\n    for (auto const& s : PROTO::shm->m_shmFormats) {\n        m_resource->sendFormat(sc<wl_shm_format>(s));\n    }\n}\n\nbool CWLSHMResource::good() {\n    return m_resource->resource();\n}\n\nCWLSHMProtocol::CWLSHMProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CWLSHMProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    if (m_shmFormats.empty()) {\n        m_shmFormats.push_back(WL_SHM_FORMAT_ARGB8888);\n        m_shmFormats.push_back(WL_SHM_FORMAT_XRGB8888);\n\n        static const std::array<DRMFormat, 6> supportedShmFourccFormats = {\n            DRM_FORMAT_XBGR8888, DRM_FORMAT_ABGR8888, DRM_FORMAT_XRGB2101010, DRM_FORMAT_ARGB2101010, DRM_FORMAT_XBGR2101010, DRM_FORMAT_ABGR2101010,\n        };\n\n        for (auto const& fmt : supportedShmFourccFormats) {\n            m_shmFormats.push_back(fmt);\n        }\n    }\n\n    const auto& RESOURCE = m_managers.emplace_back(makeUnique<CWLSHMResource>(makeUnique<CWlShm>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CWLSHMProtocol::destroyResource(CWLSHMResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLSHMProtocol::destroyResource(CWLSHMPoolResource* resource) {\n    std::erase_if(m_pools, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLSHMProtocol::destroyResource(CWLSHMBuffer* resource) {\n    std::erase_if(m_buffers, [&](const auto& other) { return other.get() == resource; });\n}\n"
  },
  {
    "path": "src/protocols/core/Shm.hpp",
    "content": "#pragma once\n\n/*\n    Implementations for:\n     - wl_shm\n     - wl_shm_pool\n     - wl_buffer with shm\n*/\n\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <memory>\n#include <vector>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include \"wayland.hpp\"\n#include \"../types/Buffer.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n\nclass CWLSHMPoolResource;\n\nclass CSHMPool {\n  public:\n    CSHMPool() = delete;\n    CSHMPool(Hyprutils::OS::CFileDescriptor fd, size_t size);\n    ~CSHMPool();\n\n    Hyprutils::OS::CFileDescriptor m_fd;\n    size_t                         m_size = 0;\n    void*                          m_data = nullptr;\n\n    void                           resize(size_t size);\n};\n\nclass CWLSHMBuffer : public IHLBuffer {\n  public:\n    CWLSHMBuffer(WP<CWLSHMPoolResource> pool, uint32_t id, int32_t offset, const Vector2D& size, int32_t stride, uint32_t fmt);\n    virtual ~CWLSHMBuffer();\n\n    virtual Aquamarine::eBufferCapability          caps();\n    virtual Aquamarine::eBufferType                type();\n    virtual void                                   update(const CRegion& damage);\n    virtual bool                                   isSynchronous();\n    virtual Aquamarine::SSHMAttrs                  shm();\n    virtual std::tuple<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags);\n    virtual void                                   endDataPtr();\n\n    bool                                           good();\n\n    int32_t                                        m_offset = 0;\n    int32_t                                        m_stride = 0;\n    uint32_t                                       m_fmt    = 0;\n    SP<CSHMPool>                                   m_pool;\n\n  private:\n    struct {\n        CHyprSignalListener bufferResourceDestroy;\n    } m_listeners;\n};\n\nclass CWLSHMPoolResource {\n  public:\n    CWLSHMPoolResource(UP<CWlShmPool>&& resource_, Hyprutils::OS::CFileDescriptor fd, size_t size);\n\n    bool                   good();\n\n    SP<CSHMPool>           m_pool;\n\n    WP<CWLSHMPoolResource> m_self;\n\n  private:\n    UP<CWlShmPool> m_resource;\n\n    friend class CWLSHMBuffer;\n};\n\nclass CWLSHMResource {\n  public:\n    CWLSHMResource(UP<CWlShm>&& resource_);\n\n    bool good();\n\n  private:\n    UP<CWlShm> m_resource;\n};\n\nclass CWLSHMProtocol : public IWaylandProtocol {\n  public:\n    CWLSHMProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CWLSHMResource* resource);\n    void destroyResource(CWLSHMPoolResource* resource);\n    void destroyResource(CWLSHMBuffer* resource);\n\n    //\n    std::vector<UP<CWLSHMResource>>     m_managers;\n    std::vector<UP<CWLSHMPoolResource>> m_pools;\n    std::vector<SP<CWLSHMBuffer>>       m_buffers;\n\n    //\n    std::vector<uint32_t> m_shmFormats;\n\n    friend class CWLSHMResource;\n    friend class CWLSHMPoolResource;\n    friend class CWLSHMBuffer;\n};\n\nnamespace PROTO {\n    inline UP<CWLSHMProtocol> shm;\n};\n"
  },
  {
    "path": "src/protocols/core/Subcompositor.cpp",
    "content": "#include \"Subcompositor.hpp\"\n#include \"Compositor.hpp\"\n#include <algorithm>\n\nCWLSubsurfaceResource::CWLSubsurfaceResource(SP<CWlSubsurface> resource_, SP<CWLSurfaceResource> surface_, SP<CWLSurfaceResource> parent_) :\n    m_surface(surface_), m_parent(parent_), m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlSubsurface* r) { destroy(); });\n    m_resource->setDestroy([this](CWlSubsurface* r) { destroy(); });\n\n    m_resource->setSetPosition([this](CWlSubsurface* r, int32_t x, int32_t y) { m_position = {x, y}; });\n\n    m_resource->setSetDesync([this](CWlSubsurface* r) { m_sync = false; });\n    m_resource->setSetSync([this](CWlSubsurface* r) { m_sync = true; });\n\n    m_resource->setPlaceAbove([this](CWlSubsurface* r, wl_resource* surf) {\n        auto SURF = CWLSurfaceResource::fromResource(surf);\n\n        if (!m_parent)\n            return;\n\n        std::erase_if(m_parent->m_subsurfaces, [this](const auto& e) { return e == m_self || !e; });\n\n        std::ranges::for_each(m_parent->m_subsurfaces, [](const auto& e) { e->m_zIndex *= 2; });\n\n        auto it = std::ranges::find_if(m_parent->m_subsurfaces, [SURF](const auto& s) { return s->m_surface == SURF; });\n\n        if ((it == m_parent->m_subsurfaces.end() && m_parent != SURF) || SURF == m_surface) {\n            // protocol error, this is not a valid surface\n            r->error(-1, \"Invalid surface in placeAbove\");\n            return;\n        }\n\n        if (it == m_parent->m_subsurfaces.end()) {\n            // parent surface\n            m_parent->m_subsurfaces.emplace_back(m_self);\n            m_zIndex = 1;\n        } else {\n            m_zIndex = (*it)->m_zIndex + 1;\n            m_parent->m_subsurfaces.emplace_back(m_self);\n        }\n\n        m_parent->sortSubsurfaces();\n    });\n\n    m_resource->setPlaceBelow([this](CWlSubsurface* r, wl_resource* surf) {\n        auto SURF = CWLSurfaceResource::fromResource(surf);\n\n        if (!m_parent)\n            return;\n\n        std::erase_if(m_parent->m_subsurfaces, [this](const auto& e) { return e == m_self || !e; });\n\n        std::ranges::for_each(m_parent->m_subsurfaces, [](const auto& e) { e->m_zIndex *= 2; });\n\n        auto it = std::ranges::find_if(m_parent->m_subsurfaces, [SURF](const auto& s) { return s->m_surface == SURF; });\n\n        if ((it == m_parent->m_subsurfaces.end() && m_parent != SURF) || SURF == m_surface) {\n            // protocol error, this is not a valid surface\n            r->error(-1, \"Invalid surface in placeBelow\");\n            return;\n        }\n\n        if (it == m_parent->m_subsurfaces.end()) {\n            // parent\n            m_parent->m_subsurfaces.emplace_back(m_self);\n            m_zIndex = -1;\n        } else {\n            m_zIndex = (*it)->m_zIndex - 1;\n            m_parent->m_subsurfaces.emplace_back(m_self);\n        }\n\n        m_parent->sortSubsurfaces();\n    });\n\n    m_listeners.commitSurface = m_surface->m_events.commit.listen([this] {\n        if (m_surface->m_current.texture && !m_surface->m_mapped) {\n            m_surface->map();\n            m_surface->m_events.map.emit();\n            return;\n        }\n\n        if (!m_surface->m_current.texture && m_surface->m_mapped) {\n            m_surface->m_events.unmap.emit();\n            m_surface->unmap();\n            return;\n        }\n    });\n}\n\nCWLSubsurfaceResource::~CWLSubsurfaceResource() {\n    m_events.destroy.emit();\n    if (m_surface)\n        m_surface->resetRole();\n}\n\nvoid CWLSubsurfaceResource::destroy() {\n    if (m_surface && m_surface->m_mapped) {\n        m_surface->m_events.unmap.emit();\n        m_surface->unmap();\n    }\n    m_events.destroy.emit();\n    PROTO::subcompositor->destroyResource(this);\n}\n\nVector2D CWLSubsurfaceResource::posRelativeToParent() {\n    Vector2D               pos  = m_position;\n    SP<CWLSurfaceResource> surf = m_parent.lock();\n\n    // some apps might create cycles, which I believe _technically_ are not a protocol error\n    // in some cases, notably firefox likes to do that, so we keep track of what\n    // surfaces we've visited and if we hit a surface we've visited we bail out.\n    std::vector<SP<CWLSurfaceResource>> surfacesVisited;\n\n    while (surf->m_role->role() == SURFACE_ROLE_SUBSURFACE && std::ranges::find_if(surfacesVisited, [surf](const auto& other) { return surf == other; }) == surfacesVisited.end()) {\n        surfacesVisited.emplace_back(surf);\n        auto subsurface = sc<CSubsurfaceRole*>(m_parent->m_role.get())->m_subsurface.lock();\n        pos += subsurface->m_position;\n        surf = subsurface->m_parent.lock();\n    }\n    return pos;\n}\n\nbool CWLSubsurfaceResource::good() {\n    return m_resource->resource();\n}\n\nSP<CWLSurfaceResource> CWLSubsurfaceResource::t1Parent() {\n    SP<CWLSurfaceResource>              surf = m_parent.lock();\n    std::vector<SP<CWLSurfaceResource>> surfacesVisited;\n\n    while (surf->m_role->role() == SURFACE_ROLE_SUBSURFACE && std::ranges::find_if(surfacesVisited, [surf](const auto& other) { return surf == other; }) == surfacesVisited.end()) {\n        surfacesVisited.emplace_back(surf);\n        auto subsurface = sc<CSubsurfaceRole*>(m_parent->m_role.get())->m_subsurface.lock();\n        surf            = subsurface->m_parent.lock();\n    }\n    return surf;\n}\n\nCWLSubcompositorResource::CWLSubcompositorResource(SP<CWlSubcompositor> resource_) : m_resource(resource_) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlSubcompositor* r) { PROTO::subcompositor->destroyResource(this); });\n    m_resource->setDestroy([this](CWlSubcompositor* r) { PROTO::subcompositor->destroyResource(this); });\n\n    m_resource->setGetSubsurface([](CWlSubcompositor* r, uint32_t id, wl_resource* surface, wl_resource* parent) {\n        auto SURF   = CWLSurfaceResource::fromResource(surface);\n        auto PARENT = CWLSurfaceResource::fromResource(parent);\n\n        if UNLIKELY (!SURF || !PARENT || SURF == PARENT) {\n            r->error(WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, \"Invalid surface/parent\");\n            return;\n        }\n\n        if UNLIKELY (SURF->m_role->role() != SURFACE_ROLE_UNASSIGNED) {\n            r->error(-1, \"Surface already has a different role\");\n            return;\n        }\n\n        SP<CWLSurfaceResource> t1Parent = nullptr;\n\n        if (PARENT->m_role->role() == SURFACE_ROLE_SUBSURFACE) {\n            auto subsurface = sc<CSubsurfaceRole*>(PARENT->m_role.get())->m_subsurface.lock();\n            t1Parent        = subsurface->t1Parent();\n        } else\n            t1Parent = PARENT;\n\n        if UNLIKELY (t1Parent == SURF) {\n            r->error(WL_SUBCOMPOSITOR_ERROR_BAD_PARENT, \"Bad parent, t1 parent == surf\");\n            return;\n        }\n\n        const auto RESOURCE =\n            PROTO::subcompositor->m_surfaces.emplace_back(makeShared<CWLSubsurfaceResource>(makeShared<CWlSubsurface>(r->client(), r->version(), id), SURF, PARENT));\n\n        if UNLIKELY (!RESOURCE->good()) {\n            r->noMemory();\n            PROTO::subcompositor->m_surfaces.pop_back();\n            return;\n        }\n\n        RESOURCE->m_self = RESOURCE;\n        SURF->m_role     = makeShared<CSubsurfaceRole>(RESOURCE);\n        PARENT->m_subsurfaces.emplace_back(RESOURCE);\n\n        LOGM(Log::DEBUG, \"New wl_subsurface with id {} at {:x}\", id, (uintptr_t)RESOURCE.get());\n\n        PARENT->m_events.newSubsurface.emit(RESOURCE);\n    });\n}\n\nbool CWLSubcompositorResource::good() {\n    return m_resource->resource();\n}\n\nCWLSubcompositorProtocol::CWLSubcompositorProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {\n    ;\n}\n\nvoid CWLSubcompositorProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {\n    const auto RESOURCE = m_managers.emplace_back(makeShared<CWLSubcompositorResource>(makeShared<CWlSubcompositor>(client, ver, id)));\n\n    if UNLIKELY (!RESOURCE->good()) {\n        wl_client_post_no_memory(client);\n        m_managers.pop_back();\n        return;\n    }\n}\n\nvoid CWLSubcompositorProtocol::destroyResource(CWLSubcompositorResource* resource) {\n    std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });\n}\n\nvoid CWLSubcompositorProtocol::destroyResource(CWLSubsurfaceResource* resource) {\n    std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; });\n}\n\nCSubsurfaceRole::CSubsurfaceRole(SP<CWLSubsurfaceResource> sub) : m_subsurface(sub) {\n    ;\n}\n"
  },
  {
    "path": "src/protocols/core/Subcompositor.hpp",
    "content": "\n#pragma once\n\n/*\n    Implementations for:\n     - wl_subsurface\n     - wl_subcompositor\n*/\n\n#include <vector>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include \"wayland.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n#include \"../types/SurfaceRole.hpp\"\n\nclass CWLSurfaceResource;\nclass CWLSubsurfaceResource;\n\nclass CSubsurfaceRole : public ISurfaceRole {\n  public:\n    CSubsurfaceRole(SP<CWLSubsurfaceResource> sub);\n\n    virtual eSurfaceRole role() {\n        return SURFACE_ROLE_SUBSURFACE;\n    }\n\n    WP<CWLSubsurfaceResource> m_subsurface;\n};\n\nclass CWLSubsurfaceResource {\n  public:\n    CWLSubsurfaceResource(SP<CWlSubsurface> resource_, SP<CWLSurfaceResource> surface_, SP<CWLSurfaceResource> parent_);\n    ~CWLSubsurfaceResource();\n\n    Vector2D                  posRelativeToParent();\n    bool                      good();\n    SP<CWLSurfaceResource>    t1Parent();\n\n    bool                      m_sync = false;\n    Vector2D                  m_position;\n\n    WP<CWLSurfaceResource>    m_surface;\n    WP<CWLSurfaceResource>    m_parent;\n\n    WP<CWLSubsurfaceResource> m_self;\n\n    int                       m_zIndex = 1; // by default, it's above\n\n    struct {\n        CSignalT<> destroy;\n    } m_events;\n\n  private:\n    SP<CWlSubsurface> m_resource;\n\n    void              destroy();\n\n    struct {\n        CHyprSignalListener commitSurface;\n    } m_listeners;\n};\n\nclass CWLSubcompositorResource {\n  public:\n    CWLSubcompositorResource(SP<CWlSubcompositor> resource_);\n\n    bool good();\n\n  private:\n    SP<CWlSubcompositor> m_resource;\n};\n\nclass CWLSubcompositorProtocol : public IWaylandProtocol {\n  public:\n    CWLSubcompositorProtocol(const wl_interface* iface, const int& ver, const std::string& name);\n\n    virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);\n\n  private:\n    void destroyResource(CWLSubcompositorResource* resource);\n    void destroyResource(CWLSubsurfaceResource* resource);\n\n    //\n    std::vector<SP<CWLSubcompositorResource>> m_managers;\n    std::vector<SP<CWLSubsurfaceResource>>    m_surfaces;\n\n    friend class CWLSubcompositorResource;\n    friend class CWLSubsurfaceResource;\n};\n\nnamespace PROTO {\n    inline UP<CWLSubcompositorProtocol> subcompositor;\n};\n"
  },
  {
    "path": "src/protocols/types/Buffer.cpp",
    "content": "#include \"Buffer.hpp\"\n\nIHLBuffer::~IHLBuffer() {\n    if (locked() && m_resource)\n        sendRelease();\n}\n\nvoid IHLBuffer::sendRelease() {\n    m_resource->sendRelease();\n    m_syncReleasers.clear();\n}\n\nvoid IHLBuffer::lock() {\n    m_locks++;\n}\n\nvoid IHLBuffer::unlock() {\n    m_locks--;\n\n    ASSERT(m_locks >= 0);\n\n    if (m_locks == 0)\n        sendRelease();\n}\n\nbool IHLBuffer::locked() {\n    return m_locks > 0;\n}\n\nvoid IHLBuffer::onBackendRelease(const std::function<void()>& fn) {\n    if (m_hlEvents.backendRelease) {\n        if (m_backendReleaseQueuedFn)\n            m_backendReleaseQueuedFn();\n        Log::logger->log(Log::DEBUG, \"backendRelease emitted early\");\n    }\n\n    m_backendReleaseQueuedFn = fn;\n\n    m_hlEvents.backendRelease = events.backendRelease.listen([this] {\n        if (m_backendReleaseQueuedFn)\n            m_backendReleaseQueuedFn();\n        m_backendReleaseQueuedFn = nullptr;\n        m_hlEvents.backendRelease.reset();\n    });\n}\n\nvoid IHLBuffer::addReleasePoint(CDRMSyncPointState& point) {\n    ASSERT(locked());\n    if (point)\n        m_syncReleasers.emplace_back(point.createSyncRelease());\n}\n\nCHLBufferReference::CHLBufferReference() : m_buffer(nullptr) {\n    ;\n}\n\nCHLBufferReference::CHLBufferReference(const CHLBufferReference& other) : m_buffer(other.m_buffer) {\n    if (m_buffer)\n        m_buffer->lock();\n}\n\nCHLBufferReference::CHLBufferReference(CHLBufferReference&& other) noexcept : m_buffer(std::move(other.m_buffer)) {\n    ;\n}\n\nCHLBufferReference::CHLBufferReference(SP<IHLBuffer> buffer_) : m_buffer(buffer_) {\n    if (m_buffer)\n        m_buffer->lock();\n}\n\nCHLBufferReference::~CHLBufferReference() {\n    if (m_buffer)\n        m_buffer->unlock();\n}\n\nCHLBufferReference& CHLBufferReference::operator=(const CHLBufferReference& other) {\n    if (m_buffer == other.m_buffer)\n        return *this; // same buffer, do nothing\n\n    if (other.m_buffer)\n        other.m_buffer->lock();\n    if (m_buffer)\n        m_buffer->unlock();\n    m_buffer = other.m_buffer;\n    return *this;\n}\n\nCHLBufferReference& CHLBufferReference::operator=(CHLBufferReference&& other) {\n    if (this != &other) {\n        if (m_buffer)\n            m_buffer->unlock();\n        m_buffer       = other.m_buffer;\n        other.m_buffer = nullptr;\n    }\n    return *this;\n}\n\nbool CHLBufferReference::operator==(const CHLBufferReference& other) const {\n    return m_buffer == other.m_buffer;\n}\n\nbool CHLBufferReference::operator==(const SP<IHLBuffer>& other) const {\n    return m_buffer == other;\n}\n\nbool CHLBufferReference::operator==(const SP<Aquamarine::IBuffer>& other) const {\n    return m_buffer == other;\n}\n\nSP<IHLBuffer> CHLBufferReference::operator->() const {\n    return m_buffer;\n}\n\nCHLBufferReference::operator bool() const {\n    return m_buffer;\n}\n\nvoid CHLBufferReference::drop() {\n    if (!m_buffer)\n        return;\n\n    m_buffer->m_locks--;\n    ASSERT(m_buffer->m_locks >= 0);\n\n    m_buffer = nullptr;\n}\n"
  },
  {
    "path": "src/protocols/types/Buffer.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include \"../../render/Texture.hpp\"\n#include \"./WLBuffer.hpp\"\n#include \"../DRMSyncobj.hpp\"\n\n#include <aquamarine/buffer/Buffer.hpp>\n\nclass CSyncReleaser;\nclass CHLBufferReference;\n\nclass IHLBuffer : public Aquamarine::IBuffer {\n  public:\n    virtual ~IHLBuffer();\n    virtual Aquamarine::eBufferCapability caps()                        = 0;\n    virtual Aquamarine::eBufferType       type()                        = 0;\n    virtual void                          update(const CRegion& damage) = 0;\n    virtual bool                          isSynchronous()               = 0; // whether the updates to this buffer are synchronous, aka happen over cpu\n    virtual bool                          good()                        = 0;\n    virtual void                          sendRelease();\n    virtual void                          lock();\n    virtual void                          unlock();\n    virtual bool                          locked();\n\n    void                                  onBackendRelease(const std::function<void()>& fn);\n    void                                  addReleasePoint(CDRMSyncPointState& point);\n\n    SP<ITexture>                          m_texture;\n    bool                                  m_opaque = false;\n    SP<CWLBufferResource>                 m_resource;\n    std::vector<UP<CSyncReleaser>>        m_syncReleasers;\n    Hyprutils::OS::CFileDescriptor        m_syncFd;\n\n    struct {\n        CHyprSignalListener backendRelease;\n        CHyprSignalListener backendRelease2; // for explicit ds\n    } m_hlEvents;\n\n  private:\n    int                   m_locks = 0;\n    std::function<void()> m_backendReleaseQueuedFn;\n\n    friend class CHLBufferReference;\n};\n\n// for ref-counting. Releases in ~dtor\nclass CHLBufferReference {\n  public:\n    CHLBufferReference();\n    CHLBufferReference(const CHLBufferReference& other);\n    CHLBufferReference(CHLBufferReference&& other) noexcept;\n    CHLBufferReference(SP<IHLBuffer> buffer);\n    ~CHLBufferReference();\n\n    CHLBufferReference& operator=(const CHLBufferReference& other);\n    CHLBufferReference& operator=(CHLBufferReference&& other);\n\n    bool                operator==(const CHLBufferReference& other) const;\n    bool                operator==(const SP<IHLBuffer>& other) const;\n    bool                operator==(const SP<Aquamarine::IBuffer>& other) const;\n    SP<IHLBuffer>       operator->() const;\n    //\n    operator bool() const;\n\n    // unlock and drop the buffer without sending release\n    void          drop();\n\n    SP<IHLBuffer> m_buffer;\n};\n"
  },
  {
    "path": "src/protocols/types/ContentType.cpp",
    "content": "#include \"ContentType.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include <hyprutils/string/String.hpp>\n#include <drm_mode.h>\n#include <format>\n\nnamespace NContentType {\n    static std::unordered_map<std::string, eContentType> const table = {\n        {\"none\", CONTENT_TYPE_NONE}, {\"photo\", CONTENT_TYPE_PHOTO}, {\"video\", CONTENT_TYPE_VIDEO}, {\"game\", CONTENT_TYPE_GAME}};\n\n    eContentType fromString(const std::string name) {\n        if (Hyprutils::String::isNumber(name)) {\n            try {\n                auto n = std::stoi(name);\n                if (n >= 0 && n <= 3)\n                    return sc<eContentType>(n);\n            } catch (std::exception& e) { Log::logger->log(Log::ERR, \"NContentType::fromString: invalid number {}, need to be between 0 and 3\", name); }\n        }\n        auto it = table.find(name);\n        if (it != table.end())\n            return it->second;\n        else\n            return CONTENT_TYPE_NONE;\n    }\n\n    std::string toString(eContentType type) {\n        for (const auto& [k, v] : table) {\n            if (v == type)\n                return k;\n        }\n        return \"\";\n    }\n\n    eContentType fromWP(wpContentTypeV1Type contentType) {\n        switch (contentType) {\n            case WP_CONTENT_TYPE_V1_TYPE_NONE: return CONTENT_TYPE_NONE;\n            case WP_CONTENT_TYPE_V1_TYPE_PHOTO: return CONTENT_TYPE_PHOTO;\n            case WP_CONTENT_TYPE_V1_TYPE_VIDEO: return CONTENT_TYPE_VIDEO;\n            case WP_CONTENT_TYPE_V1_TYPE_GAME: return CONTENT_TYPE_GAME;\n            default: return CONTENT_TYPE_NONE;\n        }\n    }\n\n    uint16_t toDRM(eContentType contentType) {\n        switch (contentType) {\n            case CONTENT_TYPE_NONE: return DRM_MODE_CONTENT_TYPE_GRAPHICS;\n            case CONTENT_TYPE_PHOTO: return DRM_MODE_CONTENT_TYPE_PHOTO;\n            case CONTENT_TYPE_VIDEO: return DRM_MODE_CONTENT_TYPE_CINEMA;\n            case CONTENT_TYPE_GAME: return DRM_MODE_CONTENT_TYPE_GAME;\n            default: return DRM_MODE_CONTENT_TYPE_NO_DATA;\n        }\n    }\n}\n"
  },
  {
    "path": "src/protocols/types/ContentType.hpp",
    "content": "#pragma once\n\n#include \"content-type-v1.hpp\"\n#include <cstdint>\n\nnamespace NContentType {\n\n    enum eContentType : uint8_t {\n        CONTENT_TYPE_NONE  = 0,\n        CONTENT_TYPE_PHOTO = 1,\n        CONTENT_TYPE_VIDEO = 2,\n        CONTENT_TYPE_GAME  = 3,\n    };\n\n    eContentType fromString(const std::string name);\n    std::string  toString(eContentType);\n    eContentType fromWP(wpContentTypeV1Type contentType);\n    uint16_t     toDRM(eContentType contentType);\n}"
  },
  {
    "path": "src/protocols/types/DMABuffer.cpp",
    "content": "#include \"DMABuffer.hpp\"\n#include \"WLBuffer.hpp\"\n#include \"../../desktop/view/LayerSurface.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../helpers/Format.hpp\"\n\n#if defined(__linux__)\n#include <linux/dma-buf.h>\n#include <linux/sync_file.h>\n#endif\n#include <sys/ioctl.h>\n\nusing namespace Hyprutils::OS;\n\nCDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs const& attrs_) : m_attrs(attrs_) {\n    m_listeners.resourceDestroy = events.destroy.listen([this] {\n        closeFDs();\n        m_listeners.resourceDestroy.reset();\n    });\n\n    size       = m_attrs.size;\n    m_resource = CWLBufferResource::create(makeShared<CWlBuffer>(client, 1, id));\n    m_opaque   = NFormatUtils::isFormatOpaque(m_attrs.format);\n    m_texture  = g_pHyprRenderer->createTexture(m_attrs, m_opaque); // texture takes ownership of the eglImage\n\n    if UNLIKELY (!m_texture) {\n        Log::logger->log(Log::ERR, \"CDMABuffer: failed to import EGLImage, retrying as implicit\");\n        m_attrs.modifier = DRM_FORMAT_MOD_INVALID;\n        m_texture        = g_pHyprRenderer->createTexture(m_attrs, m_opaque);\n\n        if UNLIKELY (!m_texture) {\n            Log::logger->log(Log::ERR, \"CDMABuffer: failed to import EGLImage\");\n            return;\n        }\n    }\n\n    m_success = m_texture->ok();\n\n    if UNLIKELY (!m_success)\n        Log::logger->log(Log::ERR, \"Failed to create a dmabuf: texture is null\");\n}\n\nCDMABuffer::~CDMABuffer() {\n    if (m_resource)\n        m_resource->sendRelease();\n\n    closeFDs();\n}\n\nAquamarine::eBufferCapability CDMABuffer::caps() {\n    return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;\n}\n\nAquamarine::eBufferType CDMABuffer::type() {\n    return Aquamarine::eBufferType::BUFFER_TYPE_DMABUF;\n}\n\nvoid CDMABuffer::update(const CRegion& damage) {\n    ;\n}\n\nbool CDMABuffer::isSynchronous() {\n    return false;\n}\n\nAquamarine::SDMABUFAttrs CDMABuffer::dmabuf() {\n    return m_attrs;\n}\n\nstd::tuple<uint8_t*, uint32_t, size_t> CDMABuffer::beginDataPtr(uint32_t flags) {\n    // FIXME:\n    return {nullptr, 0, 0};\n}\n\nvoid CDMABuffer::endDataPtr() {\n    // FIXME:\n}\n\nbool CDMABuffer::good() {\n    return m_success;\n}\n\nvoid CDMABuffer::closeFDs() {\n    for (int i = 0; i < m_attrs.planes; ++i) {\n        if (m_attrs.fds[i] == -1)\n            continue;\n        close(m_attrs.fds[i]);\n        m_attrs.fds[i] = -1;\n    }\n    m_attrs.planes = 0;\n}\n\nstatic int doIoctl(int fd, unsigned long request, void* arg) {\n    int ret;\n\n    do {\n        ret = ioctl(fd, request, arg);\n    } while (ret == -1 && (errno == EINTR || errno == EAGAIN));\n    return ret;\n}\n\n// https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html#c.dma_buf_export_sync_file\n// returns a sync file that will be signalled when dmabuf is ready to be read\nCFileDescriptor CDMABuffer::exportSyncFile() {\n    if (!good())\n        return {};\n\n#if !defined(__linux__)\n    return {};\n#else\n    std::vector<CFileDescriptor> syncFds;\n    syncFds.reserve(m_attrs.fds.size());\n\n    for (const auto& fd : m_attrs.fds) {\n        if (fd == -1)\n            continue;\n\n        // buffer readability checks are rather slow on some Intel laptops\n        // see https://gitlab.freedesktop.org/drm/intel/-/issues/9415\n        if (g_pHyprRenderer && !g_pHyprRenderer->isIntel()) {\n            if (CFileDescriptor::isReadable(fd))\n                continue;\n        }\n\n        dma_buf_export_sync_file request{\n            .flags = DMA_BUF_SYNC_READ,\n            .fd    = -1,\n        };\n\n        if (doIoctl(fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &request) == 0)\n            syncFds.emplace_back(request.fd);\n    }\n\n    if (syncFds.empty())\n        return {};\n\n    CFileDescriptor syncFd;\n    for (auto& fd : syncFds) {\n        if (!syncFd.isValid()) {\n            syncFd = std::move(fd);\n            continue;\n        }\n\n        const std::string      name = \"merged release fence\";\n        struct sync_merge_data data{\n            .name  = {}, // zero-initialize name[]\n            .fd2   = fd.get(),\n            .fence = -1,\n        };\n\n        std::ranges::copy_n(name.c_str(), std::min(name.size() + 1, sizeof(data.name)), data.name);\n\n        if (doIoctl(syncFd.get(), SYNC_IOC_MERGE, &data) == 0)\n            syncFd = CFileDescriptor(data.fence);\n        else\n            syncFd = {};\n    }\n\n    return syncFd;\n#endif\n}\n"
  },
  {
    "path": "src/protocols/types/DMABuffer.hpp",
    "content": "#pragma once\n\n#include \"Buffer.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CDMABuffer : public IHLBuffer {\n  public:\n    CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs const& attrs_);\n    virtual ~CDMABuffer();\n\n    virtual Aquamarine::eBufferCapability          caps();\n    virtual Aquamarine::eBufferType                type();\n    virtual bool                                   isSynchronous();\n    virtual void                                   update(const CRegion& damage);\n    virtual Aquamarine::SDMABUFAttrs               dmabuf();\n    virtual std::tuple<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags);\n    virtual void                                   endDataPtr();\n    bool                                           good();\n    void                                           closeFDs();\n    Hyprutils::OS::CFileDescriptor                 exportSyncFile();\n    bool                                           m_success = false;\n\n  private:\n    Aquamarine::SDMABUFAttrs m_attrs;\n\n    struct {\n        CHyprSignalListener resourceDestroy;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/protocols/types/DataDevice.cpp",
    "content": "#include \"DataDevice.hpp\"\n\nbool IDataSource::hasDnd() {\n    return false;\n}\n\nbool IDataSource::dndDone() {\n    return false;\n}\n\nbool IDataSource::used() {\n    return m_wasUsed;\n}\n\nvoid IDataSource::markUsed() {\n    m_wasUsed = true;\n}\n\neDataSourceType IDataSource::type() {\n    return DATA_SOURCE_TYPE_WAYLAND;\n}\n\nvoid IDataSource::sendDndFinished() {\n    ;\n}\n\nuint32_t IDataSource::actions() {\n    return 7; // all\n}\n\nvoid IDataSource::sendDndDropPerformed() {\n    ;\n}\n\nvoid IDataSource::sendDndAction(wl_data_device_manager_dnd_action a) {\n    ;\n}\n\nvoid IDataOffer::markDead() {\n    ;\n}\n"
  },
  {
    "path": "src/protocols/types/DataDevice.hpp",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n#include <cstdint>\n#include \"../../helpers/signal/Signal.hpp\"\n#include <wayland-server-protocol.h>\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nclass CWLDataOfferResource;\nclass CX11DataOffer;\nclass CX11DataDevice;\nclass CWLDataDeviceResource;\nclass CWLSurfaceResource;\n\nenum eDataSourceType : uint8_t {\n    DATA_SOURCE_TYPE_WAYLAND = 0,\n    DATA_SOURCE_TYPE_X11,\n};\n\nclass IDataSource {\n  public:\n    IDataSource()          = default;\n    virtual ~IDataSource() = default;\n\n    virtual std::vector<std::string> mimes()                                                          = 0;\n    virtual void                     send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd) = 0;\n    virtual void                     accepted(const std::string& mime)                                = 0;\n    virtual void                     cancelled()                                                      = 0;\n    virtual bool                     hasDnd();\n    virtual bool                     dndDone();\n    virtual void                     sendDndFinished();\n    virtual bool                     used();\n    virtual void                     markUsed();\n    virtual void                     error(uint32_t code, const std::string& msg) = 0;\n    virtual eDataSourceType          type();\n    virtual uint32_t                 actions(); // wl_data_device_manager.dnd_action\n    virtual void                     sendDndDropPerformed();\n    virtual void                     sendDndAction(wl_data_device_manager_dnd_action a);\n\n    struct {\n        CSignalT<> destroy;\n    } m_events;\n\n  private:\n    bool m_wasUsed = false;\n};\n\nclass IDataOffer {\n  public:\n    IDataOffer()          = default;\n    virtual ~IDataOffer() = default;\n\n    virtual eDataSourceType          type()       = 0;\n    virtual SP<CWLDataOfferResource> getWayland() = 0;\n    virtual SP<CX11DataOffer>        getX11()     = 0;\n    virtual SP<IDataSource>          getSource()  = 0;\n    virtual void                     markDead();\n};\n\nclass IDataDevice {\n  public:\n    IDataDevice()          = default;\n    virtual ~IDataDevice() = default;\n\n    virtual SP<CWLDataDeviceResource> getWayland()                                                                                         = 0;\n    virtual SP<CX11DataDevice>        getX11()                                                                                             = 0;\n    virtual void                      sendDataOffer(SP<IDataOffer> offer)                                                                  = 0;\n    virtual void                      sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) = 0;\n    virtual void                      sendLeave()                                                                                          = 0;\n    virtual void                      sendMotion(uint32_t timeMs, const Vector2D& local)                                                   = 0;\n    virtual void                      sendDrop()                                                                                           = 0;\n    virtual void                      sendSelection(SP<IDataOffer> offer)                                                                  = 0;\n    virtual eDataSourceType           type()                                                                                               = 0;\n};\n"
  },
  {
    "path": "src/protocols/types/SurfaceRole.hpp",
    "content": "#pragma once\n\nenum eSurfaceRole : uint8_t {\n    SURFACE_ROLE_UNASSIGNED = 0,\n    SURFACE_ROLE_XDG_SHELL,\n    SURFACE_ROLE_LAYER_SHELL,\n    SURFACE_ROLE_EASTER_EGG,\n    SURFACE_ROLE_SUBSURFACE,\n    SURFACE_ROLE_CURSOR,\n};\n\nclass ISurfaceRole {\n  public:\n    virtual eSurfaceRole role() = 0;\n    virtual ~ISurfaceRole()     = default;\n};\n"
  },
  {
    "path": "src/protocols/types/SurfaceState.cpp",
    "content": "#include \"SurfaceState.hpp\"\n#include \"helpers/Format.hpp\"\n#include \"protocols/types/Buffer.hpp\"\n#include \"render/Renderer.hpp\"\n#include \"render/Texture.hpp\"\n\nVector2D SSurfaceState::sourceSize() {\n    if UNLIKELY (!texture)\n        return {};\n\n    if UNLIKELY (viewport.hasSource)\n        return viewport.source.size();\n\n    Vector2D trc = transform % 2 == 1 ? Vector2D{bufferSize.y, bufferSize.x} : bufferSize;\n    return trc / scale;\n}\n\nCRegion SSurfaceState::accumulateBufferDamage() {\n    if (damage.empty())\n        return bufferDamage;\n\n    CRegion surfaceDamage = damage;\n    if (viewport.hasDestination) {\n        Vector2D scale = sourceSize() / viewport.destination;\n        surfaceDamage.scale(scale);\n    }\n\n    if (viewport.hasSource)\n        surfaceDamage.translate(viewport.source.pos());\n\n    Vector2D trc = transform % 2 == 1 ? Vector2D{bufferSize.y, bufferSize.x} : bufferSize;\n\n    bufferDamage = surfaceDamage.scale(scale).transform(Math::wlTransformToHyprutils(Math::invertTransform(transform)), trc.x, trc.y).add(bufferDamage);\n    damage.clear();\n    return bufferDamage;\n}\n\nvoid SSurfaceState::updateSynchronousTexture(SP<ITexture> lastTexture) {\n    auto [dataPtr, fmt, size] = buffer->beginDataPtr(0);\n    if (dataPtr) {\n        auto drmFmt = NFormatUtils::shmToDRM(fmt);\n        auto stride = bufferSize.y ? size / bufferSize.y : 0;\n        if (lastTexture && lastTexture->m_isSynchronous && lastTexture->m_size == bufferSize) {\n            texture = lastTexture;\n            texture->update(drmFmt, dataPtr, stride, accumulateBufferDamage());\n        } else\n            texture = g_pHyprRenderer->createTexture(drmFmt, dataPtr, stride, bufferSize);\n    }\n    buffer->endDataPtr();\n}\n\nvoid SSurfaceState::reset() {\n    updated.all = false;\n\n    // After commit, there is no pending buffer until the next attach.\n    buffer = {};\n\n    // applies only to the buffer that is attached to the surface\n    acquire = {};\n\n    // wl_surface.commit assigns pending ... and clears pending damage.\n    damage.clear();\n    bufferDamage.clear();\n\n    callbacks.clear();\n    lockMask = LOCK_REASON_NONE;\n\n    barrierSet    = false;\n    surfaceLocked = false;\n    fifoScheduled = false;\n\n    pendingTimeout.reset();\n    timer.reset(); // CEventLoopManager::nudgeTimers should handle it eventually\n}\n\nvoid SSurfaceState::updateFrom(SSurfaceState& ref) {\n    updated = ref.updated;\n\n    if (ref.updated.bits.buffer) {\n        buffer     = ref.buffer;\n        texture    = ref.texture;\n        size       = ref.size;\n        bufferSize = ref.bufferSize;\n    }\n\n    if (ref.updated.bits.damage) {\n        damage       = ref.damage;\n        bufferDamage = ref.bufferDamage;\n    } else {\n        // damage is always relative to the current commit\n        damage.clear();\n        bufferDamage.clear();\n    }\n\n    if (ref.updated.bits.input)\n        input = ref.input;\n\n    if (ref.updated.bits.opaque)\n        opaque = ref.opaque;\n\n    if (ref.updated.bits.offset)\n        offset = ref.offset;\n\n    if (ref.updated.bits.scale)\n        scale = ref.scale;\n\n    if (ref.updated.bits.transform)\n        transform = ref.transform;\n\n    if (ref.updated.bits.viewport)\n        viewport = ref.viewport;\n\n    if (ref.updated.bits.acquire)\n        acquire = ref.acquire;\n\n    if (ref.updated.bits.acked)\n        ackedSize = ref.ackedSize;\n\n    if (ref.updated.bits.frame) {\n        callbacks.insert(callbacks.end(), std::make_move_iterator(ref.callbacks.begin()), std::make_move_iterator(ref.callbacks.end()));\n        ref.callbacks.clear();\n    }\n\n    if (ref.barrierSet)\n        barrierSet = ref.barrierSet;\n}\n"
  },
  {
    "path": "src/protocols/types/SurfaceState.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../helpers/time/Time.hpp\"\n#include \"../../managers/eventLoop/EventLoopTimer.hpp\"\n#include \"../WaylandProtocol.hpp\"\n#include \"./Buffer.hpp\"\n\nclass ITexture;\nclass CDRMSyncPointState;\nclass CWLCallbackResource;\n\nenum eLockReason : uint8_t {\n    LOCK_REASON_NONE  = 0,\n    LOCK_REASON_FENCE = 1 << 0,\n    LOCK_REASON_FIFO  = 1 << 1,\n    LOCK_REASON_TIMER = 1 << 2\n};\n\ninline eLockReason operator|(eLockReason a, eLockReason b) {\n    return sc<eLockReason>(sc<uint8_t>(a) | sc<uint8_t>(b));\n}\ninline eLockReason operator&(eLockReason a, eLockReason b) {\n    return sc<eLockReason>(sc<uint8_t>(a) & sc<uint8_t>(b));\n}\ninline eLockReason& operator|=(eLockReason& a, eLockReason b) {\n    a = a | b;\n    return a;\n}\ninline eLockReason& operator&=(eLockReason& a, eLockReason b) {\n    a = a & b;\n    return a;\n}\ninline eLockReason operator~(eLockReason a) {\n    return sc<eLockReason>(~sc<uint8_t>(a));\n}\n\nstruct SSurfaceState {\n    union {\n        uint16_t all = 0;\n        struct {\n            bool buffer : 1;\n            bool damage : 1;\n            bool opaque : 1;\n            bool input : 1;\n            bool transform : 1;\n            bool scale : 1;\n            bool offset : 1;\n            bool viewport : 1;\n            bool acquire : 1;\n            bool acked : 1;\n            bool frame : 1;\n            bool fifo : 1;\n        } bits;\n    } updated;\n\n    bool rejected = false;\n\n    // initial values, copied from protocol text\n    CHLBufferReference  buffer = {};                                          // The initial surface contents are void\n    CRegion             damage, bufferDamage;                                 // The initial value for pending damage is empty\n    CRegion             opaque;                                               // The initial value for an opaque region is empty\n    CRegion             input     = CBox{{}, {INT32_MAX - 1, INT32_MAX - 1}}; // The initial value for an input region is infinite\n    wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;               // A newly created surface has its buffer transformation set to normal\n    int                 scale     = 1;                                        // A newly created surface has its buffer scale set to 1\n\n    // these don't have well defined initial values in the protocol, but these work\n    Vector2D size, bufferSize;\n    Vector2D offset;\n\n    // for xdg_shell resizing\n    Vector2D ackedSize;\n\n    // for wl_surface::frame callbacks.\n    std::vector<SP<CWLCallbackResource>> callbacks;\n\n    // viewporter protocol surface state\n    struct {\n        bool     hasDestination = false;\n        bool     hasSource      = false;\n        Vector2D destination;\n        CBox     source;\n    } viewport;\n    Vector2D sourceSize();\n\n    // drm syncobj protocol surface state\n    CDRMSyncPointState acquire;\n    eLockReason        lockMask = LOCK_REASON_NONE;\n\n    // texture of surface content, used for rendering\n    SP<ITexture> texture;\n    void         updateSynchronousTexture(SP<ITexture> lastTexture);\n\n    // fifo\n    bool barrierSet    = false;\n    bool surfaceLocked = false;\n    bool fifoScheduled = false;\n\n    // commit timing\n    std::optional<Time::steady_dur> pendingTimeout;\n    SP<CEventLoopTimer>             timer;\n\n    // helpers\n    CRegion accumulateBufferDamage();       // transforms state.damage and merges it into state.bufferDamage\n    void    updateFrom(SSurfaceState& ref); // updates this state based on a reference state.\n    void    reset();                        // resets pending state after commit\n};\n"
  },
  {
    "path": "src/protocols/types/SurfaceStateQueue.cpp",
    "content": "#include \"SurfaceStateQueue.hpp\"\n#include \"../core/Compositor.hpp\"\n#include \"SurfaceState.hpp\"\n\nCSurfaceStateQueue::CSurfaceStateQueue(WP<CWLSurfaceResource> surf) : m_surface(std::move(surf)) {}\n\nvoid CSurfaceStateQueue::clear() {\n    m_queue.clear();\n}\n\nWP<SSurfaceState> CSurfaceStateQueue::enqueue(UP<SSurfaceState>&& state) {\n    return m_queue.emplace_back(std::move(state));\n}\n\nvoid CSurfaceStateQueue::dropState(const WP<SSurfaceState>& state) {\n    auto it = find(state);\n    if (it == m_queue.end())\n        return;\n\n    m_queue.erase(it);\n}\n\nvoid CSurfaceStateQueue::lock(const WP<SSurfaceState>& weakState, eLockReason reason) {\n    ASSERT(reason != LOCK_REASON_NONE);\n    auto it = find(weakState);\n    if (it == m_queue.end())\n        return;\n\n    it->get()->lockMask |= reason;\n}\n\nvoid CSurfaceStateQueue::unlock(const WP<SSurfaceState>& state, eLockReason reason) {\n    ASSERT(reason != LOCK_REASON_NONE);\n    auto it = find(state);\n    if (it == m_queue.end())\n        return;\n\n    it->get()->lockMask &= ~reason;\n    tryProcess();\n}\n\nvoid CSurfaceStateQueue::unlockFirst(eLockReason reason) {\n    ASSERT(reason != LOCK_REASON_NONE);\n    for (auto& it : m_queue) {\n        if ((it->lockMask & reason) != LOCK_REASON_NONE) {\n            it->lockMask &= ~reason;\n            break;\n        }\n    }\n\n    tryProcess();\n}\n\nauto CSurfaceStateQueue::find(const WP<SSurfaceState>& state) -> std::deque<UP<SSurfaceState>>::iterator {\n    if (state.expired())\n        return m_queue.end();\n\n    auto* raw = state.get(); // get raw pointer\n\n    for (auto it = m_queue.begin(); it != m_queue.end(); ++it) {\n        if (it->get() == raw)\n            return it;\n    }\n\n    return m_queue.end();\n}\n\nvoid CSurfaceStateQueue::tryProcess() {\n    while (!m_queue.empty()) {\n        auto& front = m_queue.front();\n        if (front->lockMask & LOCK_REASON_FIFO && !m_surface->m_current.barrierSet)\n            front->lockMask &= ~LOCK_REASON_FIFO;\n\n        if (front->lockMask != LOCK_REASON_NONE)\n            return;\n\n        m_surface->commitState(*front);\n        m_queue.pop_front();\n    }\n}\n"
  },
  {
    "path": "src/protocols/types/SurfaceStateQueue.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"SurfaceState.hpp\"\n#include <deque>\n\nclass CWLSurfaceResource;\n\nclass CSurfaceStateQueue {\n  public:\n    CSurfaceStateQueue() = default;\n    explicit CSurfaceStateQueue(WP<CWLSurfaceResource> surf);\n\n    void              clear();\n    WP<SSurfaceState> enqueue(UP<SSurfaceState>&& state);\n    void              dropState(const WP<SSurfaceState>& state);\n    void              lock(const WP<SSurfaceState>& state, eLockReason reason);\n    void              unlock(const WP<SSurfaceState>& state, eLockReason reason);\n    void              unlockFirst(eLockReason reason);\n    void              tryProcess();\n\n  private:\n    std::deque<UP<SSurfaceState>>                    m_queue;\n    WP<CWLSurfaceResource>                           m_surface;\n\n    typename std::deque<UP<SSurfaceState>>::iterator find(const WP<SSurfaceState>& state);\n};\n"
  },
  {
    "path": "src/protocols/types/WLBuffer.cpp",
    "content": "#include \"WLBuffer.hpp\"\n#include \"Buffer.hpp\"\n#include \"../core/Compositor.hpp\"\n#include \"../DRMSyncobj.hpp\"\n#include \"../../helpers/sync/SyncTimeline.hpp\"\n#include <xf86drm.h>\n\nCWLBufferResource::CWLBufferResource(WP<CWlBuffer> resource_) : m_resource(resource_.lock()) {\n    if UNLIKELY (!good())\n        return;\n\n    m_resource->setOnDestroy([this](CWlBuffer* r) {\n        if (m_buffer.expired())\n            return;\n        m_buffer->events.destroy.emit();\n    });\n    m_resource->setDestroy([this](CWlBuffer* r) {\n        if (m_buffer.expired())\n            return;\n        m_buffer->events.destroy.emit();\n    });\n\n    m_resource->setData(this);\n}\n\nbool CWLBufferResource::good() {\n    return m_resource->resource();\n}\n\nvoid CWLBufferResource::sendRelease() {\n    m_resource->sendRelease();\n}\n\nwl_resource* CWLBufferResource::getResource() {\n    return m_resource->resource();\n}\n\nSP<CWLBufferResource> CWLBufferResource::fromResource(wl_resource* res) {\n    auto data = sc<CWLBufferResource*>(sc<CWlBuffer*>(wl_resource_get_user_data(res))->data());\n    return data ? data->m_self.lock() : nullptr;\n}\n\nSP<CWLBufferResource> CWLBufferResource::create(WP<CWlBuffer> resource) {\n    auto p    = SP<CWLBufferResource>(new CWLBufferResource(resource));\n    p->m_self = p;\n    return p;\n}\n"
  },
  {
    "path": "src/protocols/types/WLBuffer.hpp",
    "content": "#pragma once\n\n#include <vector>\n#include <cstdint>\n#include \"../WaylandProtocol.hpp\"\n#include \"wayland.hpp\"\n#include \"../../helpers/signal/Signal.hpp\"\n\nclass IHLBuffer;\nclass CWLSurfaceResource;\n\nclass CWLBufferResource {\n  public:\n    static SP<CWLBufferResource> create(WP<CWlBuffer> resource);\n    static SP<CWLBufferResource> fromResource(wl_resource* res);\n\n    bool                         good();\n    void                         sendRelease();\n    wl_resource*                 getResource();\n\n    WP<IHLBuffer>                m_buffer;\n\n    WP<CWLBufferResource>        m_self;\n\n  private:\n    CWLBufferResource(WP<CWlBuffer> resource_);\n\n    SP<CWlBuffer> m_resource;\n\n    friend class IHLBuffer;\n};\n"
  },
  {
    "path": "src/render/AsyncResourceGatherer.hpp",
    "content": "#pragma once\n\n#include <hyprgraphics/resource/AsyncResourceGatherer.hpp>\n#include <hyprgraphics/resource/resources/ImageResource.hpp>\n\n#include \"../helpers/memory/Memory.hpp\"\n\ninline UP<Hyprgraphics::CAsyncResourceGatherer> g_pAsyncResourceGatherer;"
  },
  {
    "path": "src/render/Framebuffer.cpp",
    "content": "#include \"Framebuffer.hpp\"\n\nIFramebuffer::IFramebuffer(const std::string& name) : m_name(name) {}\n\nbool IFramebuffer::alloc(int w, int h, uint32_t format) {\n    RASSERT((w > 0 && h > 0), \"cannot alloc a FB with negative / zero size! (attempted {}x{})\", w, h);\n\n    const bool sizeChanged   = (m_size != Vector2D(w, h));\n    const bool formatChanged = (format != m_drmFormat);\n\n    if (m_fbAllocated && !sizeChanged && !formatChanged)\n        return true;\n\n    m_size        = {w, h};\n    m_drmFormat   = format;\n    m_fbAllocated = internalAlloc(w, h, format);\n    return m_fbAllocated;\n}\n\nbool IFramebuffer::isAllocated() {\n    return m_fbAllocated && m_tex;\n}\n\nSP<ITexture> IFramebuffer::getTexture() {\n    return m_tex;\n}\n\nSP<ITexture> IFramebuffer::getStencilTex() {\n    return m_stencilTex;\n}\n"
  },
  {
    "path": "src/render/Framebuffer.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/Format.hpp\"\n#include \"Texture.hpp\"\n#include <cstdint>\n#include <drm_fourcc.h>\n\nclass CHLBufferReference;\n\nclass IFramebuffer {\n  public:\n    IFramebuffer() = default;\n    IFramebuffer(const std::string& name);\n    virtual ~IFramebuffer() = default;\n\n    virtual bool alloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888);\n    virtual void release()                                                                                                                  = 0;\n    virtual bool readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) = 0;\n\n    virtual void bind() = 0;\n\n    bool         isAllocated();\n    SP<ITexture> getTexture();\n    SP<ITexture> getStencilTex();\n\n    virtual void addStencil(SP<ITexture> tex) = 0;\n\n    Vector2D     m_size;\n    DRMFormat    m_drmFormat = DRM_FORMAT_INVALID;\n\n  protected:\n    virtual bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) = 0;\n\n    SP<ITexture> m_tex;\n    bool         m_fbAllocated = false;\n\n    SP<ITexture> m_stencilTex;\n    std::string  m_name; // name for logging\n};\n"
  },
  {
    "path": "src/render/GLRenderer.cpp",
    "content": "#include \"GLRenderer.hpp\"\n#include <aquamarine/output/Output.hpp>\n#include \"../config/ConfigValue.hpp\"\n#include \"../managers/CursorManager.hpp\"\n#include \"../managers/PointerManager.hpp\"\n#include \"../protocols/SessionLock.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../protocols/PresentationTime.hpp\"\n#include \"../protocols/core/DataDevice.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../debug/HyprDebugOverlay.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"pass/TexPassElement.hpp\"\n#include \"pass/ClearPassElement.hpp\"\n#include \"pass/RectPassElement.hpp\"\n#include \"pass/SurfacePassElement.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../protocols/types/ContentType.hpp\"\n#include \"OpenGL.hpp\"\n#include \"Renderer.hpp\"\n#include \"gl/GLFramebuffer.hpp\"\n#include \"gl/GLTexture.hpp\"\n#include \"decorations/CHyprDropShadowDecoration.hpp\"\n\n#include <cstdint>\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\nusing namespace Hyprutils::Utils;\nusing namespace Hyprutils::OS;\nusing enum NContentType::eContentType;\nusing namespace NColorManagement;\n\nextern \"C\" {\n#include <xf86drm.h>\n}\n\nCHyprGLRenderer::CHyprGLRenderer() : IHyprRenderer() {}\n\nvoid CHyprGLRenderer::initRender() {\n    g_pHyprOpenGL->makeEGLCurrent();\n    g_pHyprRenderer->m_renderData.pMonitor = renderData().pMonitor;\n}\n\nbool CHyprGLRenderer::initRenderBuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {\n    try {\n        m_currentRenderbuffer = getOrCreateRenderbuffer(m_currentBuffer, fmt);\n    } catch (std::exception& e) {\n        Log::logger->log(Log::ERR, \"getOrCreateRenderbuffer failed for {}\", NFormatUtils::drmFormatName(fmt));\n        return false;\n    }\n\n    return m_currentRenderbuffer;\n}\n\nbool CHyprGLRenderer::beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP<IFramebuffer> fb, bool simple) {\n    initRender();\n\n    RASSERT(fb, \"Cannot render FULL_FAKE without a provided fb!\");\n    fb->bind();\n    if (simple)\n        g_pHyprOpenGL->beginSimple(pMonitor, damage, nullptr, fb);\n    else\n        g_pHyprOpenGL->begin(pMonitor, damage, fb);\n    return true;\n}\n\nbool CHyprGLRenderer::beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple) {\n\n    m_currentRenderbuffer->bind();\n    if (simple)\n        g_pHyprOpenGL->beginSimple(pMonitor, damage, m_currentRenderbuffer);\n    else\n        g_pHyprOpenGL->begin(pMonitor, damage);\n\n    return true;\n}\n\nvoid CHyprGLRenderer::endRender(const std::function<void()>& renderingDoneCallback) {\n    const auto  PMONITOR           = g_pHyprRenderer->m_renderData.pMonitor;\n    static auto PNVIDIAANTIFLICKER = CConfigValue<Hyprlang::INT>(\"opengl:nvidia_anti_flicker\");\n\n    g_pHyprRenderer->m_renderData.damage = m_renderPass.render(g_pHyprRenderer->m_renderData.damage);\n\n    auto cleanup = CScopeGuard([this]() {\n        if (m_currentRenderbuffer)\n            m_currentRenderbuffer->unbind();\n        m_currentRenderbuffer = nullptr;\n        m_currentBuffer       = nullptr;\n    });\n\n    if (m_renderMode != RENDER_MODE_TO_BUFFER_READ_ONLY)\n        g_pHyprOpenGL->end();\n    else {\n        g_pHyprRenderer->m_renderData.pMonitor.reset();\n        g_pHyprRenderer->m_renderData.mouseZoomFactor   = 1.f;\n        g_pHyprRenderer->m_renderData.mouseZoomUseMouse = true;\n    }\n\n    if (m_renderMode == RENDER_MODE_FULL_FAKE)\n        return;\n\n    if (m_renderMode == RENDER_MODE_NORMAL)\n        PMONITOR->m_output->state->setBuffer(m_currentBuffer);\n\n    if (!explicitSyncSupported()) {\n        Log::logger->log(Log::TRACE, \"renderer: Explicit sync unsupported, falling back to implicit in endRender\");\n\n        // nvidia doesn't have implicit sync, so we have to explicitly wait here, llvmpipe and other software renderer seems to bug out aswell.\n        if ((isNvidia() && *PNVIDIAANTIFLICKER) || isSoftware())\n            glFinish();\n        else\n            glFlush(); // mark an implicit sync point\n\n        m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works\n        if (renderingDoneCallback)\n            renderingDoneCallback();\n\n        return;\n    }\n\n    UP<CEGLSync> eglSync = CEGLSync::create();\n    if LIKELY (eglSync && eglSync->isValid()) {\n        for (auto const& buf : m_usedAsyncBuffers) {\n            for (const auto& releaser : buf->m_syncReleasers) {\n                releaser->addSyncFileFd(eglSync->fd());\n            }\n        }\n\n        // release buffer refs with release points now, since syncReleaser handles actual buffer release based on EGLSync\n        std::erase_if(m_usedAsyncBuffers, [](const auto& buf) { return !buf->m_syncReleasers.empty(); });\n\n        // release buffer refs without release points when EGLSync sync_file/fence is signalled\n        g_pEventLoopManager->doOnReadable(eglSync->fd().duplicate(), [renderingDoneCallback, prevbfs = std::move(m_usedAsyncBuffers)]() mutable {\n            prevbfs.clear();\n            if (renderingDoneCallback)\n                renderingDoneCallback();\n        });\n        m_usedAsyncBuffers.clear();\n\n        if (m_renderMode == RENDER_MODE_NORMAL) {\n            PMONITOR->m_inFence = eglSync->takeFd();\n            PMONITOR->m_output->state->setExplicitInFence(PMONITOR->m_inFence.get());\n        }\n    } else {\n        Log::logger->log(Log::ERR, \"renderer: Explicit sync failed, releasing resources\");\n\n        m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works\n        if (renderingDoneCallback)\n            renderingDoneCallback();\n    }\n}\n\nvoid CHyprGLRenderer::renderOffToMain(IFramebuffer* off) {\n    g_pHyprOpenGL->renderOffToMain(off);\n}\n\nSP<IRenderbuffer> CHyprGLRenderer::getOrCreateRenderbufferInternal(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    return makeShared<CGLRenderbuffer>(buffer, fmt);\n}\n\nSP<ITexture> CHyprGLRenderer::createStencilTexture(const int width, const int height) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    auto tex = makeShared<CGLTexture>();\n    tex->allocate({width, height});\n\n    return tex;\n}\n\nSP<ITexture> CHyprGLRenderer::createTexture(bool opaque) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    return makeShared<CGLTexture>(opaque);\n}\n\nSP<ITexture> CHyprGLRenderer::createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    return makeShared<CGLTexture>(drmFormat, pixels, stride, size, keepDataCopy, opaque);\n}\n\nSP<ITexture> CHyprGLRenderer::createTexture(const Aquamarine::SDMABUFAttrs& attrs, bool opaque) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    const auto image = g_pHyprOpenGL->createEGLImage(attrs);\n    if (!image)\n        return nullptr;\n    return makeShared<CGLTexture>(attrs, image, opaque);\n}\n\nSP<ITexture> CHyprGLRenderer::createTexture(const int width, const int height, unsigned char* const data) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    SP<ITexture> tex = makeShared<CGLTexture>();\n\n    tex->allocate({width, height});\n\n    tex->m_size = {width, height};\n    // copy the data to an OpenGL texture we have\n    const GLint glFormat = GL_RGBA;\n    const GLint glType   = GL_UNSIGNED_BYTE;\n\n    tex->bind();\n    tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);\n    tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);\n\n    glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, data);\n    tex->unbind();\n\n    return tex;\n}\n\nSP<ITexture> CHyprGLRenderer::createTexture(cairo_surface_t* cairo) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    const auto CAIROFORMAT = cairo_image_surface_get_format(cairo);\n    auto       tex         = makeShared<CGLTexture>();\n\n    tex->allocate({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)});\n\n    const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;\n    const GLint glFormat  = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;\n    const GLint glType    = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;\n\n    const auto  DATA = cairo_image_surface_get_data(cairo);\n    tex->bind();\n    tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\n    if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {\n        tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);\n        tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);\n    }\n\n    glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA);\n\n    return tex;\n}\n\nSP<ITexture> CHyprGLRenderer::createTexture(std::span<const float> lut3D, size_t N) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    return makeShared<CGLTexture>(lut3D, N);\n}\n\nbool CHyprGLRenderer::explicitSyncSupported() {\n    return g_pHyprOpenGL->explicitSyncSupported();\n}\n\nstd::vector<SDRMFormat> CHyprGLRenderer::getDRMFormats() {\n    return g_pHyprOpenGL->getDRMFormats();\n}\n\nstd::vector<uint64_t> CHyprGLRenderer::getDRMFormatModifiers(DRMFormat format) {\n    return g_pHyprOpenGL->getDRMFormatModifiers(format);\n}\n\nSP<IFramebuffer> CHyprGLRenderer::createFB(const std::string& name) {\n    g_pHyprOpenGL->makeEGLCurrent();\n    return makeShared<CGLFramebuffer>(name);\n}\n\nvoid CHyprGLRenderer::disableScissor() {\n    g_pHyprOpenGL->scissor(nullptr);\n}\n\nvoid CHyprGLRenderer::blend(bool enabled) {\n    g_pHyprOpenGL->blend(enabled);\n}\n\nvoid CHyprGLRenderer::drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) {\n    g_pHyprOpenGL->renderRoundedShadow(box, round, roundingPower, range, color, a);\n}\n\nSP<ITexture> CHyprGLRenderer::blurFramebuffer(SP<IFramebuffer> source, float a, CRegion* originalDamage) {\n    auto src = GLFB(source);\n    return g_pHyprOpenGL->blurFramebufferWithDamage(a, originalDamage, *src)->getTexture();\n}\n\nvoid CHyprGLRenderer::setViewport(int x, int y, int width, int height) {\n    g_pHyprOpenGL->setViewport(x, y, width, height);\n}\n\nbool CHyprGLRenderer::reloadShaders(const std::string& path) {\n    return g_pHyprOpenGL->initShaders(path);\n}\n\nvoid CHyprGLRenderer::draw(CBorderPassElement* element, const CRegion& damage) {\n    const auto m_data = element->m_data;\n    if (m_data.hasGrad2)\n        g_pHyprOpenGL->renderBorder(\n            m_data.box, m_data.grad1, m_data.grad2, m_data.lerp,\n            {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound});\n    else\n        g_pHyprOpenGL->renderBorder(\n            m_data.box, m_data.grad1,\n            {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound});\n};\n\nvoid CHyprGLRenderer::draw(CClearPassElement* element, const CRegion& damage) {\n    g_pHyprOpenGL->clear(element->m_data.color);\n};\n\nvoid CHyprGLRenderer::draw(CFramebufferElement* element, const CRegion& damage) {\n    const auto       m_data = element->m_data;\n    SP<IFramebuffer> fb     = nullptr;\n\n    if (m_data.main) {\n        switch (m_data.framebufferID) {\n            case FB_MONITOR_RENDER_MAIN: fb = g_pHyprRenderer->m_renderData.mainFB; break;\n            case FB_MONITOR_RENDER_CURRENT: fb = g_pHyprRenderer->m_renderData.currentFB; break;\n            case FB_MONITOR_RENDER_OUT: fb = g_pHyprRenderer->m_renderData.outFB; break;\n            default: fb = nullptr;\n        }\n\n        if (!fb) {\n            Log::logger->log(Log::ERR, \"BUG THIS: CFramebufferElement::draw: main but null\");\n            return;\n        }\n\n    } else {\n        switch (m_data.framebufferID) {\n            case FB_MONITOR_RENDER_EXTRA_OFFLOAD: fb = g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB; break;\n            case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = m_renderData.pMonitor->m_mirrorFB; break;\n            case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = m_renderData.pMonitor->m_mirrorSwapFB; break;\n            case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB; break;\n            case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB; break;\n            case FB_MONITOR_RENDER_EXTRA_BLUR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_blurFB; break;\n            default: fb = nullptr;\n        }\n\n        if (!fb) {\n            Log::logger->log(Log::ERR, \"BUG THIS: CFramebufferElement::draw: not main but null\");\n            return;\n        }\n    }\n\n    fb->bind();\n};\n\nvoid CHyprGLRenderer::draw(CPreBlurElement* element, const CRegion& damage) {\n    auto dmg = damage;\n    g_pHyprRenderer->preBlurForCurrentMonitor(&dmg);\n};\n\nvoid CHyprGLRenderer::draw(CRectPassElement* element, const CRegion& damage) {\n    const auto m_data = element->m_data;\n\n    if (m_data.color.a == 1.F || !m_data.blur)\n        g_pHyprOpenGL->renderRect(m_data.box, m_data.color, {.damage = &damage, .round = m_data.round, .roundingPower = m_data.roundingPower});\n    else\n        g_pHyprOpenGL->renderRect(m_data.box, m_data.color,\n                                  {.round = m_data.round, .roundingPower = m_data.roundingPower, .blur = true, .blurA = m_data.blurA, .xray = m_data.xray});\n};\n\nvoid CHyprGLRenderer::draw(CShadowPassElement* element, const CRegion& damage) {\n    const auto m_data = element->m_data;\n    m_data.deco->render(g_pHyprRenderer->m_renderData.pMonitor.lock(), m_data.a);\n};\n\nvoid CHyprGLRenderer::draw(CTexPassElement* element, const CRegion& damage) {\n    const auto m_data = element->m_data;\n\n    g_pHyprOpenGL->renderTexture( //\n        m_data.tex, m_data.box,\n        {\n            // blur settings for m_data.blur == true\n            .blur                  = m_data.blur,\n            .blurA                 = m_data.blurA,\n            .overallA              = m_data.overallA,\n            .blockBlurOptimization = m_data.blockBlurOptimization.value_or(false),\n            .blurredBG             = m_data.blurredBG,\n\n            // common settings\n            .damage             = m_data.damage.empty() ? &damage : &m_data.damage,\n            .surface            = m_data.surface,\n            .a                  = m_data.a,\n            .round              = m_data.round,\n            .roundingPower      = m_data.roundingPower,\n            .discardActive      = m_data.discardActive,\n            .allowCustomUV      = m_data.allowCustomUV,\n            .cmBackToSRGB       = m_data.cmBackToSRGB,\n            .cmBackToSRGBSource = m_data.cmBackToSRGBSource,\n            .discardMode        = m_data.ignoreAlpha.has_value() ? sc<uint32_t>(DISCARD_ALPHA) : m_data.discardMode,\n            .discardOpacity     = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity,\n            .clipRegion         = m_data.clipRegion,\n            .currentLS          = m_data.currentLS,\n\n            .primarySurfaceUVTopLeft     = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft,\n            .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight,\n        });\n};\n\nvoid CHyprGLRenderer::draw(CTextureMatteElement* element, const CRegion& damage) {\n    const auto m_data = element->m_data;\n\n    g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb);\n};\n\nSP<ITexture> CHyprGLRenderer::getBlurTexture(PHLMONITORREF pMonitor) {\n    if (!pMonitor->m_blurFB)\n        return nullptr;\n    return pMonitor->m_blurFB->getTexture();\n}\n\nvoid CHyprGLRenderer::unsetEGL() {\n    if (!g_pHyprOpenGL)\n        return;\n\n    eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n}\n"
  },
  {
    "path": "src/render/GLRenderer.hpp",
    "content": "#pragma once\n\n#include \"Renderer.hpp\"\n\nclass CHyprGLRenderer : public IHyprRenderer {\n  public:\n    CHyprGLRenderer();\n\n    void                    endRender(const std::function<void()>& renderingDoneCallback = {}) override;\n    SP<ITexture>            createStencilTexture(const int width, const int height) override;\n    SP<ITexture>            createTexture(bool opaque = false) override;\n    SP<ITexture>            createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false) override;\n    SP<ITexture>            createTexture(const Aquamarine::SDMABUFAttrs&, bool opaque = false) override;\n    SP<ITexture>            createTexture(const int width, const int height, unsigned char* const data) override;\n    SP<ITexture>            createTexture(cairo_surface_t* cairo) override;\n    SP<ITexture>            createTexture(std::span<const float> lut3D, size_t N) override;\n    bool                    explicitSyncSupported() override;\n    std::vector<SDRMFormat> getDRMFormats() override;\n    std::vector<uint64_t>   getDRMFormatModifiers(DRMFormat format) override;\n    SP<IFramebuffer>        createFB(const std::string& name = \"\") override;\n    void                    disableScissor() override;\n    void                    blend(bool enabled) override;\n    void                    drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) override;\n    SP<ITexture>            blurFramebuffer(SP<IFramebuffer> source, float a, CRegion* originalDamage) override;\n    void                    setViewport(int x, int y, int width, int height) override;\n    bool                    reloadShaders(const std::string& path = \"\") override;\n\n    void                    unsetEGL();\n\n  private:\n    void              renderOffToMain(IFramebuffer* off) override;\n    SP<IRenderbuffer> getOrCreateRenderbufferInternal(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) override;\n    bool              beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple = false) override;\n    bool              beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP<IFramebuffer> fb, bool simple = false) override;\n    void              initRender() override;\n    bool              initRenderBuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) override;\n\n    void              draw(CBorderPassElement* element, const CRegion& damage) override;\n    void              draw(CClearPassElement* element, const CRegion& damage) override;\n    void              draw(CFramebufferElement* element, const CRegion& damage) override;\n    void              draw(CPreBlurElement* element, const CRegion& damage) override;\n    void              draw(CRectPassElement* element, const CRegion& damage) override;\n    void              draw(CShadowPassElement* element, const CRegion& damage) override;\n    void              draw(CTexPassElement* element, const CRegion& damage) override;\n    void              draw(CTextureMatteElement* element, const CRegion& damage) override;\n\n    SP<ITexture>      getBlurTexture(PHLMONITORREF pMonitor) override;\n\n    SP<IRenderbuffer> m_currentRenderbuffer = nullptr;\n\n    friend class CHyprOpenGLImpl;\n};\n"
  },
  {
    "path": "src/render/OpenGL.cpp",
    "content": "#include <GLES3/gl32.h>\n#include <cstdint>\n#include <hyprgraphics/color/Color.hpp>\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/memory/UniquePtr.hpp>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/path/Path.hpp>\n#include <numbers>\n#include <random>\n#include <pango/pangocairo.h>\n#include \"OpenGL.hpp\"\n#include \"Renderer.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\n#include \"../helpers/CursorShapes.hpp\"\n#include \"../helpers/TransferFunction.hpp\"\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../managers/PointerManager.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../protocols/ColorManagement.hpp\"\n#include \"../helpers/cm/ColorManagement.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../managers/CursorManager.hpp\"\n#include \"../helpers/fs/FsUtils.hpp\"\n#include \"../helpers/env/Env.hpp\"\n#include \"../helpers/MainLoopExecutor.hpp\"\n#include \"../i18n/Engine.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"../managers/screenshare/ScreenshareManager.hpp\"\n#include \"../debug/HyprNotificationOverlay.hpp\"\n#include \"hyprerror/HyprError.hpp\"\n#include \"macros.hpp\"\n#include \"pass/TexPassElement.hpp\"\n#include \"pass/RectPassElement.hpp\"\n#include \"pass/PreBlurElement.hpp\"\n#include \"pass/ClearPassElement.hpp\"\n#include \"GLRenderer.hpp\"\n#include \"Shader.hpp\"\n#include \"AsyncResourceGatherer.hpp\"\n#include <ranges>\n#include <algorithm>\n#include <fstream>\n#include <string>\n#include <xf86drm.h>\n#include <fcntl.h>\n#include <gbm.h>\n#include <filesystem>\n#include <cstring>\n#include \"./shaders/Shaders.hpp\"\n#include \"ShaderLoader.hpp\"\n#include \"Texture.hpp\"\n#include \"gl/GLFramebuffer.hpp\"\n#include \"gl/GLTexture.hpp\"\n\nusing namespace Hyprutils::OS;\nusing namespace NColorManagement;\nusing namespace Render;\n\nstatic inline void loadGLProc(void* pProc, const char* name) {\n    void* proc = rc<void*>(eglGetProcAddress(name));\n    if (proc == nullptr) {\n        Log::logger->log(Log::CRIT, \"[Tracy GPU Profiling] eglGetProcAddress({}) failed\", name);\n        abort();\n    }\n    *sc<void**>(pProc) = proc;\n}\n\nstatic enum Hyprutils::CLI::eLogLevel eglLogToLevel(EGLint type) {\n    switch (type) {\n        case EGL_DEBUG_MSG_CRITICAL_KHR: return Log::CRIT;\n        case EGL_DEBUG_MSG_ERROR_KHR: return Log::ERR;\n        case EGL_DEBUG_MSG_WARN_KHR: return Log::WARN;\n        case EGL_DEBUG_MSG_INFO_KHR: return Log::DEBUG;\n        default: return Log::DEBUG;\n    }\n}\n\nstatic const char* eglErrorToString(EGLint error) {\n    switch (error) {\n        case EGL_SUCCESS: return \"EGL_SUCCESS\";\n        case EGL_NOT_INITIALIZED: return \"EGL_NOT_INITIALIZED\";\n        case EGL_BAD_ACCESS: return \"EGL_BAD_ACCESS\";\n        case EGL_BAD_ALLOC: return \"EGL_BAD_ALLOC\";\n        case EGL_BAD_ATTRIBUTE: return \"EGL_BAD_ATTRIBUTE\";\n        case EGL_BAD_CONTEXT: return \"EGL_BAD_CONTEXT\";\n        case EGL_BAD_CONFIG: return \"EGL_BAD_CONFIG\";\n        case EGL_BAD_CURRENT_SURFACE: return \"EGL_BAD_CURRENT_SURFACE\";\n        case EGL_BAD_DISPLAY: return \"EGL_BAD_DISPLAY\";\n        case EGL_BAD_DEVICE_EXT: return \"EGL_BAD_DEVICE_EXT\";\n        case EGL_BAD_SURFACE: return \"EGL_BAD_SURFACE\";\n        case EGL_BAD_MATCH: return \"EGL_BAD_MATCH\";\n        case EGL_BAD_PARAMETER: return \"EGL_BAD_PARAMETER\";\n        case EGL_BAD_NATIVE_PIXMAP: return \"EGL_BAD_NATIVE_PIXMAP\";\n        case EGL_BAD_NATIVE_WINDOW: return \"EGL_BAD_NATIVE_WINDOW\";\n        case EGL_CONTEXT_LOST: return \"EGL_CONTEXT_LOST\";\n    }\n    return \"Unknown\";\n}\n\nstatic void eglLog(EGLenum error, const char* command, EGLint type, EGLLabelKHR thread, EGLLabelKHR obj, const char* msg) {\n    Log::logger->log(eglLogToLevel(type), \"[EGL] Command {} errored out with {} (0x{}): {}\", command, eglErrorToString(error), error, msg);\n}\n\nstatic int openRenderNode(int drmFd) {\n    auto renderName = drmGetRenderDeviceNameFromFd(drmFd);\n    if (!renderName) {\n        // This can happen on split render/display platforms, fallback to\n        // primary node\n        renderName = drmGetPrimaryDeviceNameFromFd(drmFd);\n        if (!renderName) {\n            Log::logger->log(Log::ERR, \"drmGetPrimaryDeviceNameFromFd failed\");\n            return -1;\n        }\n        Log::logger->log(Log::DEBUG, \"DRM dev {} has no render node, falling back to primary\", renderName);\n\n        drmVersion* render_version = drmGetVersion(drmFd);\n        if (render_version && render_version->name) {\n            Log::logger->log(Log::DEBUG, \"DRM dev versionName\", render_version->name);\n            if (strcmp(render_version->name, \"evdi\") == 0) {\n                free(renderName); // NOLINT(cppcoreguidelines-no-malloc)\n                renderName = strdup(\"/dev/dri/card0\");\n            }\n            drmFreeVersion(render_version);\n        }\n    }\n\n    Log::logger->log(Log::DEBUG, \"openRenderNode got drm device {}\", renderName);\n\n    int renderFD = open(renderName, O_RDWR | O_CLOEXEC);\n    if (renderFD < 0)\n        Log::logger->log(Log::ERR, \"openRenderNode failed to open drm device {}\", renderName);\n\n    free(renderName); // NOLINT(cppcoreguidelines-no-malloc)\n    return renderFD;\n}\n\nvoid CHyprOpenGLImpl::initEGL(bool gbm) {\n    std::vector<EGLint> attrs;\n    if (m_exts.KHR_display_reference) {\n        attrs.push_back(EGL_TRACK_REFERENCES_KHR);\n        attrs.push_back(EGL_TRUE);\n    }\n\n    attrs.push_back(EGL_NONE);\n\n    m_eglDisplay = m_proc.eglGetPlatformDisplayEXT(gbm ? EGL_PLATFORM_GBM_KHR : EGL_PLATFORM_DEVICE_EXT, gbm ? m_gbmDevice : m_eglDevice, attrs.data());\n    if (m_eglDisplay == EGL_NO_DISPLAY)\n        RASSERT(false, \"EGL: failed to create a platform display\");\n\n    attrs.clear();\n\n    EGLint major, minor;\n    if (eglInitialize(m_eglDisplay, &major, &minor) == EGL_FALSE)\n        RASSERT(false, \"EGL: failed to initialize a platform display\");\n\n    const std::string EGLEXTENSIONS = eglQueryString(m_eglDisplay, EGL_EXTENSIONS);\n\n    m_exts.IMG_context_priority               = EGLEXTENSIONS.contains(\"IMG_context_priority\");\n    m_exts.EXT_create_context_robustness      = EGLEXTENSIONS.contains(\"EXT_create_context_robustness\");\n    m_exts.EXT_image_dma_buf_import           = EGLEXTENSIONS.contains(\"EXT_image_dma_buf_import\");\n    m_exts.EXT_image_dma_buf_import_modifiers = EGLEXTENSIONS.contains(\"EXT_image_dma_buf_import_modifiers\");\n    m_exts.KHR_context_flush_control          = EGLEXTENSIONS.contains(\"EGL_KHR_context_flush_control\");\n\n    if (m_exts.IMG_context_priority) {\n        Log::logger->log(Log::DEBUG, \"EGL: IMG_context_priority supported, requesting high\");\n        attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);\n        attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);\n    }\n\n    if (m_exts.EXT_create_context_robustness) {\n        Log::logger->log(Log::DEBUG, \"EGL: EXT_create_context_robustness supported, requesting lose on reset\");\n        attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);\n        attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT);\n    }\n\n    if (m_exts.KHR_context_flush_control) {\n        Log::logger->log(Log::DEBUG, \"EGL: Using KHR_context_flush_control\");\n        attrs.push_back(EGL_CONTEXT_RELEASE_BEHAVIOR_KHR);\n        attrs.push_back(EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR); // or _FLUSH_KHR\n    }\n\n    auto attrsNoVer = attrs;\n\n    attrs.push_back(EGL_CONTEXT_MAJOR_VERSION);\n    attrs.push_back(3);\n    attrs.push_back(EGL_CONTEXT_MINOR_VERSION);\n    attrs.push_back(2);\n    attrs.push_back(EGL_NONE);\n\n    m_eglContext = eglCreateContext(m_eglDisplay, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data());\n    if (m_eglContext == EGL_NO_CONTEXT) {\n        Log::logger->log(Log::WARN, \"EGL: Failed to create a context with GLES3.2, retrying 3.0\");\n\n        attrs = attrsNoVer;\n        attrs.push_back(EGL_CONTEXT_MAJOR_VERSION);\n        attrs.push_back(3);\n        attrs.push_back(EGL_CONTEXT_MINOR_VERSION);\n        attrs.push_back(0);\n        attrs.push_back(EGL_NONE);\n\n        m_eglContext        = eglCreateContext(m_eglDisplay, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data());\n        m_eglContextVersion = EGL_CONTEXT_GLES_3_0;\n\n        if (m_eglContext == EGL_NO_CONTEXT)\n            RASSERT(false, \"EGL: failed to create a context with either GLES3.2 or 3.0\");\n    }\n\n    if (m_exts.IMG_context_priority) {\n        EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;\n        eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority);\n        if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG)\n            Log::logger->log(Log::ERR, \"EGL: Failed to obtain a high priority context\");\n        else\n            Log::logger->log(Log::DEBUG, \"EGL: Got a high priority context\");\n    }\n\n    eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext);\n}\n\nstatic bool drmDeviceHasName(const drmDevice* device, const std::string& name) {\n    for (size_t i = 0; i < DRM_NODE_MAX; i++) {\n        if (!(device->available_nodes & (1 << i)))\n            continue;\n\n        if (device->nodes[i] == name)\n            return true;\n    }\n    return false;\n}\n\nEGLDeviceEXT CHyprOpenGLImpl::eglDeviceFromDRMFD(int drmFD) {\n    EGLint nDevices = 0;\n    if (!m_proc.eglQueryDevicesEXT(0, nullptr, &nDevices)) {\n        Log::logger->log(Log::ERR, \"eglDeviceFromDRMFD: eglQueryDevicesEXT failed\");\n        return EGL_NO_DEVICE_EXT;\n    }\n\n    if (nDevices <= 0) {\n        Log::logger->log(Log::ERR, \"eglDeviceFromDRMFD: no devices\");\n        return EGL_NO_DEVICE_EXT;\n    }\n\n    std::vector<EGLDeviceEXT> devices;\n    devices.resize(nDevices);\n\n    if (!m_proc.eglQueryDevicesEXT(nDevices, devices.data(), &nDevices)) {\n        Log::logger->log(Log::ERR, \"eglDeviceFromDRMFD: eglQueryDevicesEXT failed (2)\");\n        return EGL_NO_DEVICE_EXT;\n    }\n\n    drmDevice* drmDev = nullptr;\n    if (int ret = drmGetDevice(drmFD, &drmDev); ret < 0) {\n        Log::logger->log(Log::ERR, \"eglDeviceFromDRMFD: drmGetDevice failed\");\n        return EGL_NO_DEVICE_EXT;\n    }\n\n    for (auto const& d : devices) {\n        auto devName = m_proc.eglQueryDeviceStringEXT(d, EGL_DRM_DEVICE_FILE_EXT);\n        if (!devName)\n            continue;\n\n        if (drmDeviceHasName(drmDev, devName)) {\n            Log::logger->log(Log::DEBUG, \"eglDeviceFromDRMFD: Using device {}\", devName);\n            drmFreeDevice(&drmDev);\n            return d;\n        }\n    }\n\n    drmFreeDevice(&drmDev);\n    Log::logger->log(Log::DEBUG, \"eglDeviceFromDRMFD: No drm devices found\");\n    return EGL_NO_DEVICE_EXT;\n}\n\nCHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd) {\n    const std::string EGLEXTENSIONS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);\n\n    Log::logger->log(Log::DEBUG, \"Supported EGL global extensions: ({}) {}\", std::ranges::count(EGLEXTENSIONS, ' '), EGLEXTENSIONS);\n\n    m_exts.KHR_display_reference = EGLEXTENSIONS.contains(\"KHR_display_reference\");\n\n    loadGLProc(&m_proc.glEGLImageTargetRenderbufferStorageOES, \"glEGLImageTargetRenderbufferStorageOES\");\n    loadGLProc(&m_proc.eglCreateImageKHR, \"eglCreateImageKHR\");\n    loadGLProc(&m_proc.eglDestroyImageKHR, \"eglDestroyImageKHR\");\n    loadGLProc(&m_proc.eglQueryDmaBufFormatsEXT, \"eglQueryDmaBufFormatsEXT\");\n    loadGLProc(&m_proc.eglQueryDmaBufModifiersEXT, \"eglQueryDmaBufModifiersEXT\");\n    loadGLProc(&m_proc.glEGLImageTargetTexture2DOES, \"glEGLImageTargetTexture2DOES\");\n    loadGLProc(&m_proc.eglDebugMessageControlKHR, \"eglDebugMessageControlKHR\");\n    loadGLProc(&m_proc.eglGetPlatformDisplayEXT, \"eglGetPlatformDisplayEXT\");\n    loadGLProc(&m_proc.eglCreateSyncKHR, \"eglCreateSyncKHR\");\n    loadGLProc(&m_proc.eglDestroySyncKHR, \"eglDestroySyncKHR\");\n    loadGLProc(&m_proc.eglDupNativeFenceFDANDROID, \"eglDupNativeFenceFDANDROID\");\n    loadGLProc(&m_proc.eglWaitSyncKHR, \"eglWaitSyncKHR\");\n\n    RASSERT(m_proc.eglCreateSyncKHR, \"Display driver doesn't support eglCreateSyncKHR\");\n    RASSERT(m_proc.eglDupNativeFenceFDANDROID, \"Display driver doesn't support eglDupNativeFenceFDANDROID\");\n    RASSERT(m_proc.eglWaitSyncKHR, \"Display driver doesn't support eglWaitSyncKHR\");\n\n    if (EGLEXTENSIONS.contains(\"EGL_EXT_device_base\") || EGLEXTENSIONS.contains(\"EGL_EXT_device_enumeration\"))\n        loadGLProc(&m_proc.eglQueryDevicesEXT, \"eglQueryDevicesEXT\");\n\n    if (EGLEXTENSIONS.contains(\"EGL_EXT_device_base\") || EGLEXTENSIONS.contains(\"EGL_EXT_device_query\")) {\n        loadGLProc(&m_proc.eglQueryDeviceStringEXT, \"eglQueryDeviceStringEXT\");\n        loadGLProc(&m_proc.eglQueryDisplayAttribEXT, \"eglQueryDisplayAttribEXT\");\n    }\n\n    static const auto GLDEBUG = CConfigValue<Hyprlang::INT>(\"debug:gl_debugging\");\n    if (EGLEXTENSIONS.contains(\"EGL_KHR_debug\") && *GLDEBUG) {\n        loadGLProc(&m_proc.eglDebugMessageControlKHR, \"eglDebugMessageControlKHR\");\n        static const EGLAttrib debugAttrs[] = {\n            EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE,\n        };\n        m_proc.eglDebugMessageControlKHR(::eglLog, debugAttrs);\n    }\n\n    RASSERT(eglBindAPI(EGL_OPENGL_ES_API) != EGL_FALSE, \"Couldn't bind to EGL's opengl ES API. This means your gpu driver f'd up. This is not a hyprland issue.\");\n\n    bool success = false;\n    if (EGLEXTENSIONS.contains(\"EXT_platform_device\") || !m_proc.eglQueryDevicesEXT || !m_proc.eglQueryDeviceStringEXT) {\n        m_eglDevice = eglDeviceFromDRMFD(m_drmFD);\n\n        if (m_eglDevice != EGL_NO_DEVICE_EXT) {\n            success = true;\n            initEGL(false);\n        }\n    }\n\n    if (!success) {\n        Log::logger->log(Log::WARN, \"EGL: EXT_platform_device or EGL_EXT_device_query not supported, using gbm\");\n        if (EGLEXTENSIONS.contains(\"KHR_platform_gbm\")) {\n            success = true;\n            m_gbmFD = CFileDescriptor{openRenderNode(m_drmFD)};\n            if (!m_gbmFD.isValid())\n                RASSERT(false, \"Couldn't open a gbm fd\");\n\n            m_gbmDevice = gbm_create_device(m_gbmFD.get());\n            if (!m_gbmDevice)\n                RASSERT(false, \"Couldn't open a gbm device\");\n\n            initEGL(true);\n        }\n    }\n\n    RASSERT(success, \"EGL does not support KHR_platform_gbm or EXT_platform_device, this is an issue with your gpu driver.\");\n\n    auto* const EXTENSIONS = rc<const char*>(glGetString(GL_EXTENSIONS));\n    RASSERT(EXTENSIONS, \"Couldn't retrieve openGL extensions!\");\n\n    m_extensions = EXTENSIONS;\n\n    Log::logger->log(Log::DEBUG, \"Creating the Hypr OpenGL Renderer!\");\n    Log::logger->log(Log::DEBUG, \"Using: {}\", rc<const char*>(glGetString(GL_VERSION)));\n    Log::logger->log(Log::DEBUG, \"Vendor: {}\", rc<const char*>(glGetString(GL_VENDOR)));\n    Log::logger->log(Log::DEBUG, \"Renderer: {}\", rc<const char*>(glGetString(GL_RENDERER)));\n    Log::logger->log(Log::DEBUG, \"Supported extensions: ({}) {}\", std::ranges::count(m_extensions, ' '), m_extensions);\n\n    m_exts.EXT_read_format_bgra = m_extensions.contains(\"GL_EXT_read_format_bgra\");\n\n    RASSERT(m_extensions.contains(\"GL_EXT_texture_format_BGRA8888\"), \"GL_EXT_texture_format_BGRA8888 support by the GPU driver is required\");\n\n    if (!m_exts.EXT_read_format_bgra)\n        Log::logger->log(Log::WARN, \"Your GPU does not support GL_EXT_read_format_bgra, this may cause issues with texture importing\");\n    if (!m_exts.EXT_image_dma_buf_import || !m_exts.EXT_image_dma_buf_import_modifiers)\n        Log::logger->log(Log::WARN, \"Your GPU does not support DMABUFs, this will possibly cause issues and will take a hit on the performance.\");\n\n    const std::string EGLEXTENSIONS_DISPLAY = eglQueryString(m_eglDisplay, EGL_EXTENSIONS);\n\n    Log::logger->log(Log::DEBUG, \"Supported EGL display extensions: ({}) {}\", std::ranges::count(EGLEXTENSIONS_DISPLAY, ' '), EGLEXTENSIONS_DISPLAY);\n\n#if defined(__linux__)\n    m_exts.EGL_ANDROID_native_fence_sync_ext = EGLEXTENSIONS_DISPLAY.contains(\"EGL_ANDROID_native_fence_sync\");\n\n    if (!m_exts.EGL_ANDROID_native_fence_sync_ext)\n        Log::logger->log(Log::WARN, \"Your GPU does not support explicit sync via the EGL_ANDROID_native_fence_sync extension.\");\n#else\n    m_exts.EGL_ANDROID_native_fence_sync_ext = false;\n    Log::logger->log(Log::WARN, \"Forcefully disabling explicit sync: BSD is missing support for proper timeline export\");\n#endif\n\n#ifdef USE_TRACY_GPU\n\n    loadGLProc(&glQueryCounter, \"glQueryCounterEXT\");\n    loadGLProc(&glGetQueryObjectiv, \"glGetQueryObjectivEXT\");\n    loadGLProc(&glGetQueryObjectui64v, \"glGetQueryObjectui64vEXT\");\n\n#endif\n\n    TRACY_GPU_CONTEXT;\n\n    initDRMFormats();\n\n    static auto P = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { preRender(mon); });\n\n    RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), \"Couldn't unset current EGL!\");\n\n    static auto addLastPressToHistory = [this](const Vector2D& pos, bool killing, bool touch) {\n        // shift the new pos and time in\n        std::ranges::rotate(m_pressedHistoryPositions, m_pressedHistoryPositions.end() - 1);\n        m_pressedHistoryPositions[0] = pos;\n\n        std::ranges::rotate(m_pressedHistoryTimers, m_pressedHistoryTimers.end() - 1);\n        m_pressedHistoryTimers[0].reset();\n\n        // shift killed flag in\n        m_pressedHistoryKilled <<= 1;\n        m_pressedHistoryKilled |= killing ? 1 : 0;\n#if POINTER_PRESSED_HISTORY_LENGTH < 32\n        m_pressedHistoryKilled &= (1 >> POINTER_PRESSED_HISTORY_LENGTH) - 1;\n#endif\n\n        // shift touch flag in\n        m_pressedHistoryTouched <<= 1;\n        m_pressedHistoryTouched |= touch ? 1 : 0;\n#if POINTER_PRESSED_HISTORY_LENGTH < 32\n        m_pressedHistoryTouched &= (1 >> POINTER_PRESSED_HISTORY_LENGTH) - 1;\n#endif\n    };\n\n    static auto P2 = Event::bus()->m_events.input.mouse.button.listen([](IPointer::SButtonEvent e, Event::SCallbackInfo&) {\n        if (e.state != WL_POINTER_BUTTON_STATE_PRESSED)\n            return;\n\n        addLastPressToHistory(g_pInputManager->getMouseCoordsInternal(), g_pInputManager->getClickMode() == CLICKMODE_KILL, false);\n    });\n\n    static auto P3 = Event::bus()->m_events.input.touch.down.listen([](ITouch::SDownEvent e, Event::SCallbackInfo&) {\n        auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : \"\");\n\n        PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor();\n\n        const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size);\n\n        addLastPressToHistory(TOUCH_COORDS, g_pInputManager->getClickMode() == CLICKMODE_KILL, true);\n    });\n\n    m_finalScreenShader = makeShared<CShader>();\n}\n\nCHyprOpenGLImpl::~CHyprOpenGLImpl() {\n    if (m_eglDisplay && m_eglContext != EGL_NO_CONTEXT)\n        eglDestroyContext(m_eglDisplay, m_eglContext);\n\n    if (m_eglDisplay)\n        eglTerminate(m_eglDisplay);\n\n    eglReleaseThread();\n\n    if (m_gbmDevice)\n        gbm_device_destroy(m_gbmDevice);\n}\n\nstd::optional<std::vector<uint64_t>> CHyprOpenGLImpl::getModsForFormat(EGLint format) {\n    // TODO: return std::expected when clang supports it\n\n    if (!m_exts.EXT_image_dma_buf_import_modifiers)\n        return std::nullopt;\n\n    EGLint len = 0;\n    if (!m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, 0, nullptr, nullptr, &len)) {\n        Log::logger->log(Log::ERR, \"EGL: Failed to query mods\");\n        return std::nullopt;\n    }\n\n    if (len <= 0)\n        return std::vector<uint64_t>{};\n\n    std::vector<uint64_t>   mods;\n    std::vector<EGLBoolean> external;\n\n    mods.resize(len);\n    external.resize(len);\n\n    m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, len, mods.data(), external.data(), &len);\n\n    std::vector<uint64_t> result;\n    // reserve number of elements to avoid reallocations\n    result.reserve(mods.size());\n\n    bool linearIsExternal = false;\n    for (size_t i = 0; i < std::min(mods.size(), external.size()); ++i) {\n        if (external[i]) {\n            if (mods[i] == DRM_FORMAT_MOD_LINEAR)\n                linearIsExternal = true;\n            continue;\n        }\n\n        result.push_back(mods[i]);\n    }\n\n    // if the driver doesn't mark linear as external, add it. It's allowed unless the driver says otherwise. (e.g. nvidia)\n    if (!linearIsExternal && std::ranges::find(mods, DRM_FORMAT_MOD_LINEAR) == mods.end())\n        result.push_back(DRM_FORMAT_MOD_LINEAR);\n\n    return result;\n}\n\nvoid CHyprOpenGLImpl::initDRMFormats() {\n    const auto DISABLE_MODS = Env::envEnabled(\"HYPRLAND_EGL_NO_MODIFIERS\");\n    if (DISABLE_MODS)\n        Log::logger->log(Log::WARN, \"HYPRLAND_EGL_NO_MODIFIERS set, disabling modifiers\");\n\n    if (!m_exts.EXT_image_dma_buf_import) {\n        Log::logger->log(Log::ERR, \"EGL: No dmabuf import, DMABufs will not work.\");\n        return;\n    }\n\n    std::vector<EGLint> formats;\n\n    if (!m_exts.EXT_image_dma_buf_import_modifiers || !m_proc.eglQueryDmaBufFormatsEXT) {\n        formats.push_back(DRM_FORMAT_ARGB8888);\n        formats.push_back(DRM_FORMAT_XRGB8888);\n        Log::logger->log(Log::WARN, \"EGL: No mod support\");\n    } else {\n        EGLint len = 0;\n        m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, 0, nullptr, &len);\n        formats.resize(len);\n        m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, len, formats.data(), &len);\n    }\n\n    if (formats.empty()) {\n        Log::logger->log(Log::ERR, \"EGL: Failed to get formats, DMABufs will not work.\");\n        return;\n    }\n\n    Log::logger->log(Log::DEBUG, \"Supported DMA-BUF formats:\");\n\n    std::vector<SDRMFormat> dmaFormats;\n    // reserve number of elements to avoid reallocations\n    dmaFormats.reserve(formats.size());\n\n    for (auto const& fmt : formats) {\n        std::vector<uint64_t> mods;\n        if (!DISABLE_MODS) {\n            auto ret = getModsForFormat(fmt);\n            if (!ret.has_value())\n                continue;\n\n            mods = *ret;\n        } else\n            mods = {DRM_FORMAT_MOD_LINEAR};\n\n        m_hasModifiers = m_hasModifiers || !mods.empty();\n\n        // EGL can always do implicit modifiers.\n        mods.push_back(DRM_FORMAT_MOD_INVALID);\n\n        dmaFormats.push_back(SDRMFormat{\n            .drmFormat = fmt,\n            .modifiers = mods,\n        });\n\n        std::vector<std::pair<uint64_t, std::string>> modifierData;\n        // reserve number of elements to avoid reallocations\n        modifierData.reserve(mods.size());\n\n        auto fmtName = drmGetFormatName(fmt);\n        Log::logger->log(Log::DEBUG, \"EGL: GPU Supports Format {} (0x{:x})\", fmtName ? fmtName : \"?unknown?\", fmt);\n        for (auto const& mod : mods) {\n            auto modName = drmGetFormatModifierName(mod);\n            modifierData.emplace_back(std::make_pair<>(mod, modName ? modName : \"?unknown?\"));\n            free(modName); // NOLINT(cppcoreguidelines-no-malloc)\n        }\n        free(fmtName); // NOLINT(cppcoreguidelines-no-malloc)\n\n        mods.clear();\n        std::ranges::sort(modifierData, [](const auto& a, const auto& b) {\n            if (a.first == 0)\n                return false;\n            if (a.second.contains(\"DCC\"))\n                return false;\n            return true;\n        });\n\n        for (auto const& [m, name] : modifierData) {\n            Log::logger->log(Log::DEBUG, \"EGL: | with modifier {} (0x{:x})\", name, m);\n            mods.emplace_back(m);\n        }\n    }\n\n    Log::logger->log(Log::DEBUG, \"EGL: {} formats found in total. Some modifiers may be omitted as they are external-only.\", dmaFormats.size());\n\n    if (dmaFormats.empty())\n        Log::logger->log(\n            Log::WARN, \"EGL: WARNING: No dmabuf formats were found, dmabuf will be disabled. This will degrade performance, but is most likely a driver issue or a very old GPU.\");\n\n    m_drmFormats = dmaFormats;\n}\n\nEGLImageKHR CHyprOpenGLImpl::createEGLImage(const Aquamarine::SDMABUFAttrs& attrs) {\n    std::array<EGLint, 50> attribs;\n    size_t                 idx = 0;\n\n    attribs[idx++] = EGL_WIDTH;\n    attribs[idx++] = attrs.size.x;\n    attribs[idx++] = EGL_HEIGHT;\n    attribs[idx++] = attrs.size.y;\n    attribs[idx++] = EGL_LINUX_DRM_FOURCC_EXT;\n    attribs[idx++] = attrs.format;\n\n    struct {\n        EGLint fd;\n        EGLint offset;\n        EGLint pitch;\n        EGLint modlo;\n        EGLint modhi;\n    } attrNames[4] = {\n        {EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT},\n        {EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT},\n        {EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT},\n        {EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}};\n\n    for (int i = 0; i < attrs.planes; ++i) {\n        attribs[idx++] = attrNames[i].fd;\n        attribs[idx++] = attrs.fds[i];\n        attribs[idx++] = attrNames[i].offset;\n        attribs[idx++] = attrs.offsets[i];\n        attribs[idx++] = attrNames[i].pitch;\n        attribs[idx++] = attrs.strides[i];\n\n        if (m_hasModifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) {\n            attribs[idx++] = attrNames[i].modlo;\n            attribs[idx++] = sc<uint32_t>(attrs.modifier & 0xFFFFFFFF);\n            attribs[idx++] = attrNames[i].modhi;\n            attribs[idx++] = sc<uint32_t>(attrs.modifier >> 32);\n        }\n    }\n\n    attribs[idx++] = EGL_IMAGE_PRESERVED_KHR;\n    attribs[idx++] = EGL_TRUE;\n    attribs[idx++] = EGL_NONE;\n\n    RASSERT(idx <= attribs.size(), \"createEglImage: attribs array out of bounds.\");\n\n    EGLImageKHR image = m_proc.eglCreateImageKHR(m_eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());\n    if (image == EGL_NO_IMAGE_KHR) {\n        Log::logger->log(Log::ERR, \"EGL: EGLCreateImageKHR failed: {}\", eglGetError());\n        return EGL_NO_IMAGE_KHR;\n    }\n\n    return image;\n}\n\nvoid CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP<IRenderbuffer> rb, SP<IFramebuffer> fb) {\n    g_pHyprRenderer->m_renderData.pMonitor = pMonitor;\n\n    const GLenum RESETSTATUS = glGetGraphicsResetStatus();\n    if (RESETSTATUS != GL_NO_ERROR) {\n        std::string errStr = \"\";\n        switch (RESETSTATUS) {\n            case GL_GUILTY_CONTEXT_RESET: errStr = \"GL_GUILTY_CONTEXT_RESET\"; break;\n            case GL_INNOCENT_CONTEXT_RESET: errStr = \"GL_INNOCENT_CONTEXT_RESET\"; break;\n            case GL_UNKNOWN_CONTEXT_RESET: errStr = \"GL_UNKNOWN_CONTEXT_RESET\"; break;\n            default: errStr = \"UNKNOWN??\"; break;\n        }\n        RASSERT(false, \"Aborting, glGetGraphicsResetStatus returned {}. Cannot continue until proper GPU reset handling is implemented.\", errStr);\n        return;\n    }\n\n    TRACY_GPU_ZONE(\"RenderBeginSimple\");\n\n    const auto FBO = rb ? rb->getFB() : fb;\n\n    setViewport(0, 0, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y);\n\n    if (!m_shadersInitialized)\n        initShaders();\n\n    g_pHyprRenderer->m_renderData.transformDamage = true;\n    g_pHyprRenderer->m_renderData.damage.set(damage);\n    g_pHyprRenderer->m_renderData.finalDamage.set(damage);\n\n    m_fakeFrame = true;\n\n    g_pHyprRenderer->m_renderData.currentFB = FBO;\n    FBO->bind();\n    m_offloadedFramebuffer = false;\n\n    g_pHyprRenderer->m_renderData.mainFB = g_pHyprRenderer->m_renderData.currentFB;\n    g_pHyprRenderer->m_renderData.outFB  = FBO;\n\n    g_pHyprRenderer->pushMonitorTransformEnabled(false);\n}\n\nvoid CHyprOpenGLImpl::makeEGLCurrent() {\n    if (!g_pCompositor || !g_pHyprOpenGL)\n        return;\n\n    if (eglGetCurrentContext() != g_pHyprOpenGL->m_eglContext)\n        eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, g_pHyprOpenGL->m_eglContext);\n}\n\nvoid CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SP<IFramebuffer> fb, std::optional<CRegion> finalDamage) {\n    g_pHyprRenderer->m_renderData.pMonitor = pMonitor;\n\n    const GLenum RESETSTATUS = glGetGraphicsResetStatus();\n    if (RESETSTATUS != GL_NO_ERROR) {\n        std::string errStr = \"\";\n        switch (RESETSTATUS) {\n            case GL_GUILTY_CONTEXT_RESET: errStr = \"GL_GUILTY_CONTEXT_RESET\"; break;\n            case GL_INNOCENT_CONTEXT_RESET: errStr = \"GL_INNOCENT_CONTEXT_RESET\"; break;\n            case GL_UNKNOWN_CONTEXT_RESET: errStr = \"GL_UNKNOWN_CONTEXT_RESET\"; break;\n            default: errStr = \"UNKNOWN??\"; break;\n        }\n        RASSERT(false, \"Aborting, glGetGraphicsResetStatus returned {}. Cannot continue until proper GPU reset handling is implemented.\", errStr);\n        return;\n    }\n\n    TRACY_GPU_ZONE(\"RenderBegin\");\n\n    setViewport(0, 0, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y);\n\n    if (!m_shadersInitialized)\n        initShaders();\n\n    const bool HAS_MIRROR_FB = g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB && g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->isAllocated();\n    const bool NEEDS_COPY_FB = g_pHyprRenderer->needsACopyFB(g_pHyprRenderer->m_renderData.pMonitor.lock());\n\n    g_pHyprRenderer->m_renderData.transformDamage = true;\n    if (HAS_MIRROR_FB != NEEDS_COPY_FB) {\n        // force full damage because the mirror fb will be empty\n        g_pHyprRenderer->m_renderData.damage.set({0, 0, INT32_MAX, INT32_MAX});\n        g_pHyprRenderer->m_renderData.finalDamage.set(g_pHyprRenderer->m_renderData.damage);\n    } else {\n        g_pHyprRenderer->m_renderData.damage.set(damage_);\n        g_pHyprRenderer->m_renderData.finalDamage.set(finalDamage.value_or(damage_));\n    }\n\n    m_fakeFrame = fb;\n\n    if (g_pHyprRenderer->m_reloadScreenShader) {\n        g_pHyprRenderer->m_reloadScreenShader = false;\n        static auto PSHADER                   = CConfigValue<std::string>(\"decoration:screen_shader\");\n        applyScreenShader(*PSHADER);\n    }\n\n    g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB->bind();\n    g_pHyprRenderer->m_renderData.currentFB = g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB;\n    m_offloadedFramebuffer                  = true;\n\n    g_pHyprRenderer->m_renderData.mainFB = g_pHyprRenderer->m_renderData.currentFB;\n    g_pHyprRenderer->m_renderData.outFB  = fb ? fb : dc<CHyprGLRenderer*>(g_pHyprRenderer.get())->m_currentRenderbuffer->getFB();\n\n    g_pHyprRenderer->pushMonitorTransformEnabled(false);\n}\n\nvoid CHyprOpenGLImpl::end() {\n    static auto PZOOMDISABLEAA = CConfigValue<Hyprlang::INT>(\"cursor:zoom_disable_aa\");\n    auto&       m_renderData   = g_pHyprRenderer->m_renderData;\n    TRACY_GPU_ZONE(\"RenderEnd\");\n\n    g_pHyprRenderer->m_renderData.currentWindow.reset();\n    g_pHyprRenderer->m_renderData.surface.reset();\n    g_pHyprRenderer->m_renderData.clipBox = {};\n\n    // end the render, copy the data to the main framebuffer\n    if LIKELY (m_offloadedFramebuffer) {\n        g_pHyprRenderer->m_renderData.damage = g_pHyprRenderer->m_renderData.finalDamage;\n        g_pHyprRenderer->pushMonitorTransformEnabled(true);\n\n        CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};\n\n        if LIKELY (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && g_pHyprRenderer->m_renderData.mouseZoomFactor == 1.0f)\n            m_renderData.pMonitor->m_zoomController.m_resetCameraState = true;\n        m_renderData.pMonitor->m_zoomController.applyZoomTransform(monbox, m_renderData);\n\n        m_applyFinalShader = !g_pHyprRenderer->m_renderData.blockScreenShader;\n        if UNLIKELY (g_pHyprRenderer->m_renderData.mouseZoomFactor != 1.F && g_pHyprRenderer->m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA)\n            g_pHyprRenderer->m_renderData.useNearestNeighbor = true;\n\n        // copy the damaged areas into the mirror buffer\n        // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring\n        if UNLIKELY (g_pHyprRenderer->needsACopyFB(g_pHyprRenderer->m_renderData.pMonitor.lock()) && !m_fakeFrame)\n            saveBufferForMirror(monbox);\n\n        g_pHyprRenderer->m_renderData.outFB->bind();\n        blend(false);\n\n        const auto PRIMITIVE_BLOCKED = m_finalScreenShader->program() >= 1 || g_pHyprRenderer->m_crashingInProgress ||\n            g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription->value() != SImageDescription{};\n\n        if LIKELY (!PRIMITIVE_BLOCKED || g_pHyprRenderer->m_renderMode != RENDER_MODE_NORMAL)\n            renderTexturePrimitive(g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB->getTexture(), monbox);\n        else // we need to use renderTexture if we do any CM whatsoever.\n            renderTexture(g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB->getTexture(), monbox, {.finalMonitorCM = true});\n\n        blend(true);\n\n        g_pHyprRenderer->m_renderData.useNearestNeighbor = false;\n        m_applyFinalShader                               = false;\n        g_pHyprRenderer->popMonitorTransformEnabled();\n    }\n\n    // invalidate our render FBs to signal to the driver we don't need them anymore\n    if (g_pHyprRenderer->m_renderData.pMonitor->m_mirrorFB) {\n        g_pHyprRenderer->m_renderData.pMonitor->m_mirrorFB->bind();\n        GLFB(g_pHyprRenderer->m_renderData.pMonitor->m_mirrorFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0});\n    }\n    if (g_pHyprRenderer->m_renderData.pMonitor->m_mirrorSwapFB) {\n        g_pHyprRenderer->m_renderData.pMonitor->m_mirrorSwapFB->bind();\n        GLFB(g_pHyprRenderer->m_renderData.pMonitor->m_mirrorSwapFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0});\n    }\n    if (g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB) {\n        g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB->bind();\n        GLFB(g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0});\n    }\n    if (g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB) {\n        g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB->bind();\n        GLFB(g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0});\n    }\n\n    // reset our data\n    m_renderData.pMonitor.reset();\n    g_pHyprRenderer->m_renderData.mouseZoomFactor   = 1.f;\n    g_pHyprRenderer->m_renderData.mouseZoomUseMouse = true;\n    g_pHyprRenderer->m_renderData.blockScreenShader = false;\n    g_pHyprRenderer->m_renderData.currentFB         = nullptr;\n    g_pHyprRenderer->m_renderData.mainFB            = nullptr;\n    g_pHyprRenderer->m_renderData.outFB             = nullptr;\n    g_pHyprRenderer->popMonitorTransformEnabled();\n\n    // if we dropped to offMain, release it now.\n    // if there is a plugin constantly using it, this might be a bit slow,\n    // but I haven't seen a single plugin yet use these, so it's better to drop a bit of vram.\n    if UNLIKELY (g_pHyprRenderer->m_renderData.pMonitor && g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB &&\n                 g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB->isAllocated())\n        g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB->release();\n\n    static const auto GLDEBUG = CConfigValue<Hyprlang::INT>(\"debug:gl_debugging\");\n\n    if (*GLDEBUG) {\n        // check for gl errors\n        const GLenum ERR = glGetError();\n\n        if UNLIKELY (ERR == GL_CONTEXT_LOST) /* We don't have infra to recover from this */\n            RASSERT(false, \"glGetError at Opengl::end() returned GL_CONTEXT_LOST. Cannot continue until proper GPU reset handling is implemented.\");\n    }\n}\n\nstatic const std::vector<std::string> SHADER_INCLUDES = {\n    \"defines.h\",   \"constants.h\", \"cm_helpers.glsl\",  \"rounding.glsl\", \"CM.glsl\",    \"tonemap.glsl\",    \"gain.glsl\",\n    \"border.glsl\", \"shadow.glsl\", \"blurprepare.glsl\", \"blur1.glsl\",    \"blur2.glsl\", \"blurFinish.glsl\",\n};\n\n// order matters, see ePreparedFragmentShader\nconst std::array<std::string, SH_FRAG_LAST> FRAG_SHADERS = {\n    \"quad.frag\",        \"passthru.frag\",   \"rgbamatte.frag\", \"ext.frag\",     \"blur1.frag\",  \"blur2.frag\",\n    \"blurprepare.frag\", \"blurfinish.frag\", \"shadow.frag\",    \"surface.frag\", \"border.frag\", \"glitch.frag\",\n};\n\nbool CHyprOpenGLImpl::initShaders(const std::string& path) {\n    auto              shaders = makeShared<SPreparedShaders>();\n    static const auto PCM     = CConfigValue<Hyprlang::INT>(\"render:cm_enabled\");\n\n    try {\n        auto shaderLoader = makeUnique<CShaderLoader>(SHADER_INCLUDES, FRAG_SHADERS, path);\n\n        shaders->TEXVERTSRC    = shaderLoader->process(\"tex300.vert\");\n        shaders->TEXVERTSRC320 = shaderLoader->process(\"tex320.vert\");\n\n        m_cmSupported = *PCM;\n\n        g_pShaderLoader = std::move(shaderLoader);\n\n    } catch (const std::exception& e) {\n        if (!m_shadersInitialized)\n            throw e;\n\n        Log::logger->log(Log::ERR, \"Shaders update failed: {}\", e.what());\n        return false;\n    }\n\n    m_shaders            = shaders;\n    m_shadersInitialized = true;\n\n    Log::logger->log(Log::DEBUG, \"Shaders initialized successfully.\");\n    return true;\n}\n\nvoid CHyprOpenGLImpl::applyScreenShader(const std::string& path) {\n\n    static auto PDT = CConfigValue<Hyprlang::INT>(\"debug:damage_tracking\");\n\n    m_finalScreenShader->destroy();\n\n    if (path.empty() || path == STRVAL_EMPTY)\n        return;\n\n    std::string     absPath = absolutePath(path, g_pConfigManager->getMainConfigPath());\n\n    std::error_code ec;\n    if (!std::filesystem::is_regular_file(absPath, ec)) {\n        if (ec)\n            g_pConfigManager->addParseError(\"Screen shader parser: Failed to check screen shader path: \" + ec.message());\n        else\n            g_pConfigManager->addParseError(\"Screen shader parser: Screen shader path is not a regular file\");\n        return;\n    }\n\n    std::ifstream infile(absPath);\n\n    if (!infile.good()) {\n        g_pConfigManager->addParseError(\"Screen shader parser: Failed to open screen shader\");\n        return;\n    }\n\n    std::string fragmentShader((std::istreambuf_iterator<char>(infile)), (std::istreambuf_iterator<char>()));\n\n    if (!m_finalScreenShader->createProgram(              //\n            fragmentShader.starts_with(\"#version 320 es\") // do not break existing custom shaders\n                ?\n                m_shaders->TEXVERTSRC320 :\n                m_shaders->TEXVERTSRC,\n            fragmentShader, true)) {\n        // Error will have been sent by now by the underlying cause\n        return;\n    }\n\n    if (m_finalScreenShader->getUniformLocation(SHADER_TIME) != -1)\n        m_finalScreenShader->setInitialTime(g_pHyprRenderer->m_globalTimer.getSeconds());\n\n    static auto uniformRequireNoDamage = [this](eShaderUniform uniform, const std::string& name) {\n        if (*PDT == 0)\n            return;\n        if (m_finalScreenShader->getUniformLocation(uniform) == -1)\n            return;\n\n        // The screen shader uses the uniform\n        // Since the screen shader could change every frame, damage tracking *needs* to be disabled\n        g_pConfigManager->addParseError(std::format(\"Screen shader: Screen shader uses uniform '{}', which requires debug:damage_tracking to be switched off.\\n\"\n                                                    \"WARNING:(Disabling damage tracking will *massively* increase GPU utilization!\",\n                                                    name));\n    };\n\n    // Allow glitch shader to use time uniform whighout damage tracking\n    if (!g_pHyprRenderer->m_crashingInProgress)\n        uniformRequireNoDamage(SHADER_TIME, \"time\");\n\n    uniformRequireNoDamage(SHADER_POINTER, \"pointer_position\");\n    uniformRequireNoDamage(SHADER_POINTER_PRESSED_POSITIONS, \"pointer_pressed_positions\");\n    uniformRequireNoDamage(SHADER_POINTER_PRESSED_TIMES, \"pointer_pressed_times\");\n    uniformRequireNoDamage(SHADER_POINTER_PRESSED_KILLED, \"pointer_pressed_killed\");\n    uniformRequireNoDamage(SHADER_POINTER_PRESSED_TOUCHED, \"pointer_pressed_touched\");\n    uniformRequireNoDamage(SHADER_POINTER_LAST_ACTIVE, \"pointer_last_active\");\n    uniformRequireNoDamage(SHADER_POINTER_HIDDEN, \"pointer_hidden\");\n    uniformRequireNoDamage(SHADER_POINTER_KILLING, \"pointer_killing\");\n    uniformRequireNoDamage(SHADER_POINTER_SHAPE, \"pointer_shape\");\n    uniformRequireNoDamage(SHADER_POINTER_SHAPE_PREVIOUS, \"pointer_shape_previous\");\n}\n\nvoid CHyprOpenGLImpl::clear(const CHyprColor& color) {\n    RASSERT(g_pHyprRenderer->m_renderData.pMonitor, \"Tried to render without begin()!\");\n\n    TRACY_GPU_ZONE(\"RenderClear\");\n\n    GLCALL(glClearColor(color.r, color.g, color.b, color.a));\n\n    if (!g_pHyprRenderer->m_renderData.damage.empty()) {\n        g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) {\n            scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n            glClear(GL_COLOR_BUFFER_BIT);\n        });\n    }\n}\n\nvoid CHyprOpenGLImpl::blend(bool enabled) {\n    if (enabled) {\n        setCapStatus(GL_BLEND, true);\n        GLCALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // everything is premultiplied\n    } else\n        setCapStatus(GL_BLEND, false);\n\n    m_blend = enabled;\n}\n\nvoid CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    RASSERT(m_renderData.pMonitor, \"Tried to scissor without begin()!\");\n\n    // only call glScissor if the box has changed\n    static CBox m_lastScissorBox = {};\n\n    if (transform) {\n        CBox       box = originalBox;\n        const auto TR  = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform));\n        box.transform(TR, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y);\n\n        if (box != m_lastScissorBox) {\n            GLCALL(glScissor(box.x, box.y, box.width, box.height));\n            m_lastScissorBox = box;\n        }\n\n        setCapStatus(GL_SCISSOR_TEST, true);\n        return;\n    }\n\n    if (originalBox != m_lastScissorBox) {\n        GLCALL(glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height));\n        m_lastScissorBox = originalBox;\n    }\n\n    setCapStatus(GL_SCISSOR_TEST, true);\n}\n\nvoid CHyprOpenGLImpl::scissor(const pixman_box32* pBox, bool transform) {\n    RASSERT(g_pHyprRenderer->m_renderData.pMonitor, \"Tried to scissor without begin()!\");\n\n    if (!pBox) {\n        setCapStatus(GL_SCISSOR_TEST, false);\n        return;\n    }\n\n    CBox newBox = {pBox->x1, pBox->y1, pBox->x2 - pBox->x1, pBox->y2 - pBox->y1};\n\n    scissor(newBox, transform);\n}\n\nvoid CHyprOpenGLImpl::scissor(const int x, const int y, const int w, const int h, bool transform) {\n    CBox box = {x, y, w, h};\n    scissor(box, transform);\n}\n\nvoid CHyprOpenGLImpl::renderRect(const CBox& box, const CHyprColor& col, SRectRenderData data) {\n    if (!data.damage)\n        data.damage = &g_pHyprRenderer->m_renderData.damage;\n\n    if (data.blur)\n        renderRectWithBlurInternal(box, col, data);\n    else\n        renderRectWithDamageInternal(box, col, data);\n}\n\nvoid CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprColor& col, const SRectRenderData& data) {\n    if (data.damage->empty())\n        return;\n\n    CRegion damage{g_pHyprRenderer->m_renderData.damage};\n    damage.intersect(box);\n\n    auto POUTFB = data.xray ? (g_pHyprRenderer->m_renderData.pMonitor->m_blurFB ? g_pHyprRenderer->m_renderData.pMonitor->m_blurFB->getTexture() : nullptr) :\n                              g_pHyprRenderer->blurMainFramebuffer(data.blurA, &damage);\n\n    g_pHyprRenderer->m_renderData.currentFB->bind();\n\n    CBox MONITORBOX = {0, 0, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.x, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.y};\n    g_pHyprRenderer->pushMonitorTransformEnabled(true);\n    const auto SAVEDRENDERMODIF               = g_pHyprRenderer->m_renderData.renderModif;\n    g_pHyprRenderer->m_renderData.renderModif = {}; // fix shit\n    renderTexture(POUTFB, MONITORBOX,\n                  STextureRenderData{.damage = &damage, .a = data.blurA, .round = data.round, .roundingPower = 2.F, .allowCustomUV = false, .allowDim = false, .noAA = false});\n    g_pHyprRenderer->popMonitorTransformEnabled();\n    g_pHyprRenderer->m_renderData.renderModif = SAVEDRENDERMODIF;\n\n    renderRectWithDamageInternal(box, col, data);\n}\n\nvoid CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprColor& col, const SRectRenderData& data) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    RASSERT((box.width > 0 && box.height > 0), \"Tried to render rect with width/height < 0!\");\n    RASSERT(m_renderData.pMonitor, \"Tried to render rect without begin()!\");\n\n    TRACY_GPU_ZONE(\"RenderRectWithDamage\");\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);\n\n    auto        shader = useShader(getShaderVariant(SH_FRAG_QUAD, data.round > 0 ? SH_FEAT_ROUNDING : 0));\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n\n    // premultiply the color as well as we don't work with straight alpha\n    shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a);\n\n    CBox transformedBox = box;\n    transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                             m_renderData.pMonitor->m_transformedSize.y);\n\n    const auto TOPLEFT  = Vector2D(transformedBox.x, transformedBox.y);\n    const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);\n\n    // Rounded corners\n    shader->setUniformFloat2(SHADER_TOP_LEFT, sc<float>(TOPLEFT.x), sc<float>(TOPLEFT.y));\n    shader->setUniformFloat2(SHADER_FULL_SIZE, sc<float>(FULLSIZE.x), sc<float>(FULLSIZE.y));\n    shader->setUniformFloat(SHADER_RADIUS, data.round);\n    shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower);\n\n    glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n    if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) {\n        CRegion damageClip{g_pHyprRenderer->m_renderData.clipBox.x, g_pHyprRenderer->m_renderData.clipBox.y, g_pHyprRenderer->m_renderData.clipBox.width,\n                           g_pHyprRenderer->m_renderData.clipBox.height};\n        damageClip.intersect(*data.damage);\n\n        if (!damageClip.empty()) {\n            damageClip.forEachRect([this](const auto& RECT) {\n                scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n            });\n        }\n    } else {\n        data.damage->forEachRect([this](const auto& RECT) {\n            scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n        });\n    }\n\n    glBindVertexArray(0);\n    scissor(nullptr);\n}\n\nvoid CHyprOpenGLImpl::renderTexture(SP<ITexture> tex, const CBox& box, STextureRenderData data) {\n    RASSERT(g_pHyprRenderer->m_renderData.pMonitor, \"Tried to render texture without begin()!\");\n\n    if (!data.damage) {\n        if (g_pHyprRenderer->m_renderData.damage.empty())\n            return;\n\n        data.damage = &g_pHyprRenderer->m_renderData.damage;\n    }\n\n    if (data.blur)\n        renderTextureWithBlurInternal(tex, box, data);\n    else\n        renderTextureInternal(tex, box, data);\n\n    scissor(nullptr);\n}\n\nstatic std::map<std::pair<uint32_t, uint32_t>, std::array<GLfloat, 9>> primariesConversionCache;\n\nstatic bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {\n    // might be too strict\n    return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||\n            imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22) &&\n        (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||\n         targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG);\n}\n\nstatic bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {\n    // might be too strict\n    return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||\n            imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG) &&\n        (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||\n         targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);\n}\n\nvoid CHyprOpenGLImpl::passCMUniforms(WP<CShader> shader, const NColorManagement::PImageDescription imageDescription,\n                                     const NColorManagement::PImageDescription targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) {\n    const auto settings = g_pHyprRenderer->getCMSettings(imageDescription, targetImageDescription,\n                                                         g_pHyprRenderer->m_renderData.surface.valid() ? g_pHyprRenderer->m_renderData.surface.lock() : nullptr, modifySDR,\n                                                         sdrMinLuminance, sdrMaxLuminance);\n\n    shader->setUniformInt(SHADER_SOURCE_TF, settings.sourceTF);\n    shader->setUniformInt(SHADER_TARGET_TF, settings.targetTF);\n    shader->setUniformFloat2(SHADER_SRC_TF_RANGE, settings.srcTFRange.min, settings.srcTFRange.max);\n    shader->setUniformFloat2(SHADER_DST_TF_RANGE, settings.dstTFRange.min, settings.dstTFRange.max);\n    shader->setUniformFloat(SHADER_SRC_REF_LUMINANCE, settings.srcRefLuminance);\n    shader->setUniformFloat(SHADER_DST_REF_LUMINANCE, settings.dstRefLuminance);\n    shader->setUniformFloat(SHADER_MAX_LUMINANCE, settings.maxLuminance);\n    shader->setUniformFloat(SHADER_DST_MAX_LUMINANCE, settings.dstMaxLuminance);\n    shader->setUniformFloat(SHADER_SDR_SATURATION, settings.sdrSaturation);\n    shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, settings.sdrBrightnessMultiplier);\n\n    if (!targetImageDescription->value().icc.present) {\n        const auto cacheKey = std::make_pair(imageDescription->id(), targetImageDescription->id());\n        if (!primariesConversionCache.contains(cacheKey)) {\n            const auto&                  mat             = settings.convertMatrix;\n            const std::array<GLfloat, 9> glConvertMatrix = {\n                mat[0][0], mat[1][0], mat[2][0], //\n                mat[0][1], mat[1][1], mat[2][1], //\n                mat[0][2], mat[1][2], mat[2][2], //\n            };\n            primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix));\n        }\n        shader->setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]);\n\n        const auto                   mat                  = settings.dstPrimaries2XYZ;\n        const std::array<GLfloat, 9> glTargetPrimariesXYZ = {\n            mat[0][0], mat[1][0], mat[2][0], //\n            mat[0][1], mat[1][1], mat[2][1], //\n            mat[0][2], mat[1][2], mat[2][2], //\n        };\n        shader->setUniformMatrix3fv(SHADER_TARGET_PRIMARIES_XYZ, 1, false, glTargetPrimariesXYZ);\n    } else {\n        // TODO: this sucks\n        GLCALL(glActiveTexture(GL_TEXTURE8));\n        targetImageDescription->value().icc.lutTexture->bind();\n\n        shader->setUniformInt(SHADER_LUT_3D, 8);\n        shader->setUniformFloat(SHADER_LUT_SIZE, targetImageDescription->value().icc.lutSize);\n\n        GLCALL(glActiveTexture(GL_TEXTURE0));\n    }\n}\n\nvoid CHyprOpenGLImpl::passCMUniforms(WP<CShader> shader, const PImageDescription imageDescription) {\n    passCMUniforms(shader, imageDescription, g_pHyprRenderer->workBufferImageDescription(), true, g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance,\n                   g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance);\n}\n\nWP<CShader> CHyprOpenGLImpl::renderToOutputInternal() {\n    static const auto PDT            = CConfigValue<Hyprlang::INT>(\"debug:damage_tracking\");\n    static const auto PCURSORTIMEOUT = CConfigValue<Hyprlang::FLOAT>(\"cursor:inactive_timeout\");\n\n    auto&             m_renderData = g_pHyprRenderer->m_renderData;\n\n    WP<CShader>       shader =\n        g_pHyprRenderer->m_crashingInProgress ? getShaderVariant(SH_FRAG_GLITCH) : (m_finalScreenShader->program() ? m_finalScreenShader : getShaderVariant(SH_FRAG_PASSTHRURGBA));\n\n    shader = useShader(shader);\n\n    if (*PDT == 0 || g_pHyprRenderer->m_crashingInProgress)\n        shader->setUniformFloat(SHADER_TIME, g_pHyprRenderer->m_globalTimer.getSeconds() - shader->getInitialTime());\n    else\n        shader->setUniformFloat(SHADER_TIME, 0.f);\n\n    shader->setUniformInt(SHADER_WL_OUTPUT, m_renderData.pMonitor->m_id);\n    shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y);\n    shader->setUniformFloat(SHADER_POINTER_INACTIVE_TIMEOUT, *PCURSORTIMEOUT);\n    shader->setUniformInt(SHADER_POINTER_HIDDEN, g_pHyprRenderer->m_cursorHiddenByCondition);\n    shader->setUniformInt(SHADER_POINTER_KILLING, g_pInputManager->getClickMode() == CLICKMODE_KILL);\n    shader->setUniformInt(SHADER_POINTER_SHAPE, g_pHyprRenderer->m_lastCursorData.shape);\n    shader->setUniformInt(SHADER_POINTER_SHAPE_PREVIOUS, g_pHyprRenderer->m_lastCursorData.shapePrevious);\n    shader->setUniformFloat(SHADER_POINTER_SIZE, g_pCursorManager->getScaledSize());\n\n    if (*PDT == 0) {\n        PHLMONITORREF pMonitor = m_renderData.pMonitor;\n        Vector2D      p        = ((g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position) * pMonitor->m_scale);\n        p                      = p.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize);\n        shader->setUniformFloat2(SHADER_POINTER, p.x / pMonitor->m_pixelSize.x, p.y / pMonitor->m_pixelSize.y);\n\n        std::vector<float> pressedPos = m_pressedHistoryPositions | std::views::transform([&](const Vector2D& vec) {\n                                            Vector2D pPressed = ((vec - pMonitor->m_position) * pMonitor->m_scale);\n                                            pPressed          = pPressed.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize);\n                                            return std::array<float, 2>{pPressed.x / pMonitor->m_pixelSize.x, pPressed.y / pMonitor->m_pixelSize.y};\n                                        }) |\n            std::views::join | std::ranges::to<std::vector<float>>();\n\n        shader->setUniform2fv(SHADER_POINTER_PRESSED_POSITIONS, pressedPos.size(), pressedPos);\n\n        std::vector<float> pressedTime =\n            m_pressedHistoryTimers | std::views::transform([](const CTimer& timer) { return timer.getSeconds(); }) | std::ranges::to<std::vector<float>>();\n\n        shader->setUniform1fv(SHADER_POINTER_PRESSED_TIMES, pressedTime.size(), pressedTime);\n\n        shader->setUniformInt(SHADER_POINTER_PRESSED_KILLED, m_pressedHistoryKilled);\n        shader->setUniformInt(SHADER_POINTER_PRESSED_TOUCHED, m_pressedHistoryTouched);\n\n        shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, g_pInputManager->m_lastCursorMovement.getSeconds());\n        shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, g_pHyprRenderer->m_lastCursorData.switchedTimer.getSeconds());\n\n    } else {\n        shader->setUniformFloat2(SHADER_POINTER, 0.f, 0.f);\n\n        static const std::vector<float> pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f);\n        static const std::vector<float> pressedTimeDefault(POINTER_PRESSED_HISTORY_LENGTH, 0.f);\n\n        shader->setUniform2fv(SHADER_POINTER_PRESSED_POSITIONS, pressedPosDefault.size(), pressedPosDefault);\n        shader->setUniform1fv(SHADER_POINTER_PRESSED_TIMES, pressedTimeDefault.size(), pressedTimeDefault);\n        shader->setUniformInt(SHADER_POINTER_PRESSED_KILLED, 0);\n\n        shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, 0.f);\n        shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, 0.f);\n    }\n\n    if (g_pHyprRenderer->m_crashingInProgress) {\n        shader->setUniformFloat(SHADER_DISTORT, g_pHyprRenderer->m_crashingDistort);\n        shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y);\n    }\n\n    return shader;\n}\n\nWP<CShader> CHyprOpenGLImpl::renderToFBInternal(const STextureRenderData& data, eTextureType texType, const CBox& newBox) {\n    static const auto  PPASS     = CConfigValue<Hyprlang::INT>(\"render:cm_fs_passthrough\");\n    static const auto  PENABLECM = CConfigValue<Hyprlang::INT>(\"render:cm_enabled\");\n    static auto        PBLEND    = CConfigValue<Hyprlang::INT>(\"render:use_shader_blur_blend\");\n\n    auto&              m_renderData = g_pHyprRenderer->m_renderData;\n\n    float              alpha = std::clamp(data.a, 0.f, 1.f);\n\n    WP<CShader>        shader;\n    ShaderFeatureFlags shaderFeatures = 0;\n\n    switch (texType) {\n        case TEXTURE_RGBA: shaderFeatures |= SH_FEAT_RGBA; break;\n        case TEXTURE_RGBX: shaderFeatures &= ~SH_FEAT_RGBA; break;\n\n        // TODO set correct features\n        case TEXTURE_EXTERNAL: shader = getShaderVariant(SH_FRAG_EXT, SH_FEAT_ROUNDING | SH_FEAT_DISCARD | SH_FEAT_TINT); break; // might be unused\n        default: RASSERT(false, \"tex->m_iTarget unsupported!\");\n    }\n\n    if (data.finalMonitorCM || (g_pHyprRenderer->m_renderData.currentWindow && g_pHyprRenderer->m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault()))\n        shaderFeatures &= ~SH_FEAT_RGBA;\n\n    const auto surface           = g_pHyprRenderer->m_renderData.surface;\n    const bool isHDRSurface      = surface.valid() && surface->m_colorManagement.valid() ? surface->m_colorManagement->isHDR() : false;\n    const bool canPassHDRSurface = isHDRSurface && !surface->m_colorManagement->isWindowsScRGB(); // windows scRGB requires CM shader\n\n    const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->workBufferImageDescription();\n\n    // chosenSdrEotf contains the valid eotf for this display\n\n    const auto SOURCE_IMAGE_DESCRIPTION = [&] {\n        // if valid CM surface, use that as a source\n        if (g_pHyprRenderer->m_renderData.surface.valid() && g_pHyprRenderer->m_renderData.surface->m_colorManagement.valid())\n            return CImageDescription::from(g_pHyprRenderer->m_renderData.surface->m_colorManagement->imageDescription());\n\n        // otherwise, if we are CM'ing back into source, use chosen, because that's what our work buffer is in\n        // the same applies to the final monitor CM\n        if (data.cmBackToSRGB || data.finalMonitorCM) // NOLINTNEXTLINE\n            return WORK_BUFFER_IMAGE_DESCRIPTION;\n\n        // otherwise, default\n        return DEFAULT_IMAGE_DESCRIPTION;\n    }();\n\n    const auto TARGET_IMAGE_DESCRIPTION = [&] {\n        // if we are CM'ing back, use default sRGB\n        if (data.cmBackToSRGB)\n            return DEFAULT_IMAGE_DESCRIPTION;\n\n        // for final CM, use the target description\n        if (data.finalMonitorCM)\n            return g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription;\n        // otherwise, use chosen, we're drawing into the work buffer\n        // NOLINTNEXTLINE\n        return WORK_BUFFER_IMAGE_DESCRIPTION;\n    }();\n\n    if (data.blur && *PBLEND && data.blurredBG)\n        shaderFeatures |= SH_FEAT_BLUR;\n\n    if (data.discardActive)\n        shaderFeatures |= SH_FEAT_DISCARD;\n\n    const bool CANT_CHECK_CM_EQUALITY =\n        data.cmBackToSRGB || data.finalMonitorCM || (!g_pHyprRenderer->m_renderData.surface || !g_pHyprRenderer->m_renderData.surface->m_colorManagement);\n\n    const bool skipCM = !*PENABLECM || !m_cmSupported                                                    /* CM unsupported or disabled */\n        || g_pHyprRenderer->m_renderData.pMonitor->doesNoShaderCM()                                      /* no shader needed */\n        || (SOURCE_IMAGE_DESCRIPTION->id() == TARGET_IMAGE_DESCRIPTION->id() && !CANT_CHECK_CM_EQUALITY) /* Source and target have the same image description */\n        || (((*PPASS && canPassHDRSurface) ||\n             (*PPASS == 1 && !isHDRSurface && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR_EDID)) &&\n            m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */;\n\n    if (data.allowDim && g_pHyprRenderer->m_renderData.currentWindow &&\n        (g_pHyprRenderer->m_renderData.currentWindow->m_notRespondingTint->value() > 0 || g_pHyprRenderer->m_renderData.currentWindow->m_dimPercent->value() > 0))\n        shaderFeatures |= SH_FEAT_TINT;\n\n    if (data.round > 0)\n        shaderFeatures |= SH_FEAT_ROUNDING;\n\n    if (!skipCM) {\n        shaderFeatures |= SH_FEAT_CM;\n\n        if (TARGET_IMAGE_DESCRIPTION->value().icc.present)\n            shaderFeatures |= SH_FEAT_ICC;\n        else {\n            const bool  needsSDRmod     = isSDR2HDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value());\n            const bool  needsHDRmod     = !needsSDRmod && isHDR2SDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value());\n            const float maxLuminance    = needsHDRmod ?\n                SOURCE_IMAGE_DESCRIPTION->value().getTFMaxLuminance(-1) :\n                (SOURCE_IMAGE_DESCRIPTION->value().luminances.max > 0 ? SOURCE_IMAGE_DESCRIPTION->value().luminances.max : SOURCE_IMAGE_DESCRIPTION->value().luminances.reference);\n            const auto  dstMaxLuminance = TARGET_IMAGE_DESCRIPTION->value().luminances.max > 0 ? TARGET_IMAGE_DESCRIPTION->value().luminances.max : 10000;\n\n            if (maxLuminance >= dstMaxLuminance * 1.01)\n                shaderFeatures |= SH_FEAT_TONEMAP;\n\n            if (!data.finalMonitorCM &&\n                (SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB ||\n                 SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) &&\n                TARGET_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ &&\n                ((g_pHyprRenderer->m_renderData.pMonitor->m_sdrSaturation > 0 && g_pHyprRenderer->m_renderData.pMonitor->m_sdrSaturation != 1.0f) ||\n                 (g_pHyprRenderer->m_renderData.pMonitor->m_sdrBrightness > 0 && g_pHyprRenderer->m_renderData.pMonitor->m_sdrBrightness != 1.0f)))\n                shaderFeatures |= SH_FEAT_SDR_MOD;\n        }\n    }\n\n    if (!shader)\n        shader = getShaderVariant(SH_FRAG_SURFACE, shaderFeatures);\n    shader = useShader(shader);\n\n    if (!skipCM) {\n        if (data.finalMonitorCM || data.cmBackToSRGB)\n            passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, true, g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance,\n                           g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance);\n        else\n            passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION);\n    }\n\n    shader->setUniformFloat(SHADER_ALPHA, alpha);\n\n    if (shaderFeatures & SH_FEAT_BLUR) {\n        shader->setUniformInt(SHADER_BLURRED_BG, 1);\n        shader->setUniformFloat2(SHADER_UV_OFFSET, newBox.x / data.blurredBG->m_size.x, newBox.y / data.blurredBG->m_size.y);\n        shader->setUniformFloat2(SHADER_UV_SIZE, newBox.width / data.blurredBG->m_size.x, newBox.height / data.blurredBG->m_size.y);\n\n        glActiveTexture(GL_TEXTURE0 + 1);\n        data.blurredBG->bind();\n    }\n\n    if (data.discardActive) {\n        shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(data.discardMode & DISCARD_OPAQUE));\n        shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(data.discardMode & DISCARD_ALPHA));\n        shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, data.discardOpacity);\n    } else {\n        shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0);\n        shader->setUniformInt(SHADER_DISCARD_ALPHA, 0);\n    }\n\n    CBox transformedBox = newBox;\n    transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                             m_renderData.pMonitor->m_transformedSize.y);\n\n    const auto TOPLEFT  = Vector2D(transformedBox.x, transformedBox.y);\n    const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);\n    // Rounded corners\n    shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y);\n    shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y);\n    shader->setUniformFloat(SHADER_RADIUS, data.round);\n    shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower);\n\n    if (data.allowDim && g_pHyprRenderer->m_renderData.currentWindow) {\n        if (g_pHyprRenderer->m_renderData.currentWindow->m_notRespondingTint->value() > 0) {\n            const auto DIM = g_pHyprRenderer->m_renderData.currentWindow->m_notRespondingTint->value();\n            shader->setUniformInt(SHADER_APPLY_TINT, 1);\n            shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM);\n        } else if (g_pHyprRenderer->m_renderData.currentWindow->m_dimPercent->value() > 0) {\n            shader->setUniformInt(SHADER_APPLY_TINT, 1);\n            const auto DIM = g_pHyprRenderer->m_renderData.currentWindow->m_dimPercent->value();\n            shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM);\n        } else\n            shader->setUniformInt(SHADER_APPLY_TINT, 0);\n    } else\n        shader->setUniformInt(SHADER_APPLY_TINT, 0);\n\n    return shader;\n}\n\nvoid CHyprOpenGLImpl::renderTextureInternal(SP<ITexture> tex, const CBox& box, const STextureRenderData& data) {\n    RASSERT(g_pHyprRenderer->m_renderData.pMonitor, \"Tried to render texture without begin()!\");\n    RASSERT((tex->ok()), \"Attempted to draw nullptr texture!\");\n\n    TRACY_GPU_ZONE(\"RenderTextureInternalWithDamage\");\n\n    if (data.damage->empty())\n        return;\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    // get the needed transform for this texture\n    const auto                  MONITOR_INVERTED = Math::wlTransformToHyprutils(Math::invertTransform(g_pHyprRenderer->m_renderData.pMonitor->m_transform));\n    Hyprutils::Math::eTransform TRANSFORM        = tex->m_transform;\n\n    if (g_pHyprRenderer->monitorTransformEnabled())\n        TRANSFORM = Math::composeTransform(MONITOR_INVERTED, TRANSFORM);\n\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox, TRANSFORM);\n\n    const bool  renderToOutput = m_applyFinalShader && g_pHyprRenderer->workBufferImageDescription()->id() == g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription->id();\n\n    glActiveTexture(GL_TEXTURE0);\n    tex->bind();\n\n    tex->setTexParameter(GL_TEXTURE_WRAP_S, data.wrapX);\n    tex->setTexParameter(GL_TEXTURE_WRAP_T, data.wrapY);\n\n    if (g_pHyprRenderer->m_renderData.useNearestNeighbor) {\n        tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n        tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n    } else {\n        tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter);\n        tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter);\n    }\n\n    auto shader = renderToOutput ? renderToOutputInternal() : renderToFBInternal(data, tex->m_type, newBox);\n\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n    shader->setUniformInt(SHADER_TEX, 0);\n    GLCALL(glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)));\n    GLCALL(glBindBuffer(GL_ARRAY_BUFFER, shader->getUniformLocation(SHADER_SHADER_VBO)));\n\n    // this tells GPU can keep reading the old block for previous draws while the CPU writes to a new one.\n    // to avoid stalls if renderTextureInternal is called multiple times on same renderpass\n    // at the cost of some temporar vram usage.\n    glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), nullptr, GL_DYNAMIC_DRAW);\n\n    auto verts = fullVerts;\n\n    if (data.allowCustomUV && data.primarySurfaceUVTopLeft != Vector2D(-1, -1)) {\n        const float u0 = data.primarySurfaceUVTopLeft.x;\n        const float v0 = data.primarySurfaceUVTopLeft.y;\n        const float u1 = data.primarySurfaceUVBottomRight.x;\n        const float v1 = data.primarySurfaceUVBottomRight.y;\n\n        verts[0].u = u0;\n        verts[0].v = v0;\n        verts[1].u = u0;\n        verts[1].v = v1;\n        verts[2].u = u1;\n        verts[2].v = v0;\n        verts[3].u = u1;\n        verts[3].v = v1;\n    }\n\n    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts.data());\n\n    if (!g_pHyprRenderer->m_renderData.clipBox.empty() || !data.clipRegion.empty()) {\n        CRegion damageClip = g_pHyprRenderer->m_renderData.clipBox;\n\n        if (!data.clipRegion.empty()) {\n            if (g_pHyprRenderer->m_renderData.clipBox.empty())\n                damageClip = data.clipRegion;\n            else\n                damageClip.intersect(data.clipRegion);\n        }\n\n        if (!damageClip.empty()) {\n            damageClip.forEachRect([this](const auto& RECT) {\n                scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n            });\n        }\n    } else {\n        data.damage->forEachRect([this](const auto& RECT) {\n            scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n        });\n    }\n\n    GLCALL(glBindVertexArray(0));\n    GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0));\n    tex->unbind();\n}\n\nvoid CHyprOpenGLImpl::renderTexturePrimitive(SP<ITexture> tex, const CBox& box) {\n    RASSERT(g_pHyprRenderer->m_renderData.pMonitor, \"Tried to render texture without begin()!\");\n    RASSERT((tex->ok()), \"Attempted to draw nullptr texture!\");\n\n    TRACY_GPU_ZONE(\"RenderTexturePrimitive\");\n\n    if (g_pHyprRenderer->m_renderData.damage.empty())\n        return;\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    // get transform\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);\n\n    glActiveTexture(GL_TEXTURE0);\n    tex->bind();\n\n    // ensure the final blit uses the desired sampling filter\n    // when cursor zoom is active we want nearest-neighbor (no anti-aliasing)\n    if (g_pHyprRenderer->m_renderData.useNearestNeighbor) {\n        tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n        tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n    } else {\n        tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter);\n        tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter);\n    }\n\n    auto shader = useShader(getShaderVariant(SH_FRAG_PASSTHRURGBA));\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n    shader->setUniformInt(SHADER_TEX, 0);\n    glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n    g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) {\n        scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    });\n\n    scissor(nullptr);\n    glBindVertexArray(0);\n    tex->unbind();\n}\n\nvoid CHyprOpenGLImpl::renderTextureMatte(SP<ITexture> tex, const CBox& box, SP<IFramebuffer> matte) {\n    RASSERT(g_pHyprRenderer->m_renderData.pMonitor, \"Tried to render texture without begin()!\");\n    RASSERT((tex->ok()), \"Attempted to draw nullptr texture!\");\n\n    TRACY_GPU_ZONE(\"RenderTextureMatte\");\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    // get transform\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);\n\n    auto        shader = useShader(getShaderVariant(SH_FRAG_MATTE));\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n    shader->setUniformInt(SHADER_TEX, 0);\n    shader->setUniformInt(SHADER_ALPHA_MATTE, 1);\n\n    glActiveTexture(GL_TEXTURE0);\n    tex->bind();\n\n    glActiveTexture(GL_TEXTURE0 + 1);\n    auto matteTex = matte->getTexture();\n    matteTex->bind();\n\n    glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n    g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) {\n        scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    });\n\n    scissor(nullptr);\n    glBindVertexArray(0);\n    tex->unbind();\n}\n\n// This probably isn't the fastest\n// but it works... well, I guess?\n//\n// Dual (or more) kawase blur\nSP<IFramebuffer> CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CGLFramebuffer& source) {\n    TRACY_GPU_ZONE(\"RenderBlurFramebufferWithDamage\");\n    auto&      m_renderData = g_pHyprRenderer->m_renderData;\n\n    const auto BLENDBEFORE = m_blend;\n    blend(false);\n    setCapStatus(GL_STENCIL_TEST, false);\n\n    // get transforms for the full monitor\n    const auto  TRANSFORM  = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform));\n    CBox        MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};\n\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(MONITORBOX, TRANSFORM);\n\n    // get the config settings\n    static auto PBLURSIZE             = CConfigValue<Hyprlang::INT>(\"decoration:blur:size\");\n    static auto PBLURPASSES           = CConfigValue<Hyprlang::INT>(\"decoration:blur:passes\");\n    static auto PBLURVIBRANCY         = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:vibrancy\");\n    static auto PBLURVIBRANCYDARKNESS = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:vibrancy_darkness\");\n\n    const auto  BLUR_PASSES = std::clamp(*PBLURPASSES, sc<int64_t>(1), sc<int64_t>(8));\n\n    // prep damage\n    CRegion damage{*originalDamage};\n    damage.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                     m_renderData.pMonitor->m_transformedSize.y);\n    damage.expand(std::clamp(*PBLURSIZE, sc<int64_t>(1), sc<int64_t>(40)) * pow(2, BLUR_PASSES));\n\n    // helper\n    const auto PMIRRORFB     = g_pHyprRenderer->m_renderData.pMonitor->m_mirrorFB;\n    const auto PMIRRORSWAPFB = g_pHyprRenderer->m_renderData.pMonitor->m_mirrorSwapFB;\n\n    auto       currentRenderToFB = PMIRRORFB;\n\n    // Begin with base color adjustments - global brightness and contrast\n    // TODO: make this a part of the first pass maybe to save on a drawcall?\n    {\n        static auto PBLURCONTRAST   = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:contrast\");\n        static auto PBLURBRIGHTNESS = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:brightness\");\n        static auto PBLEND          = CConfigValue<Hyprlang::INT>(\"render:use_shader_blur_blend\");\n\n        PMIRRORSWAPFB->bind();\n\n        glActiveTexture(GL_TEXTURE0);\n\n        auto currentTex = source.getTexture();\n\n        currentTex->bind();\n        currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\n        WP<CShader> shader;\n\n        // From FB to sRGB\n        const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id();\n        if (!skipCM) {\n            shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE, SH_FEAT_CM));\n            passCMUniforms(shader, m_renderData.pMonitor->m_imageDescription, DEFAULT_IMAGE_DESCRIPTION);\n            shader->setUniformFloat(SHADER_SDR_SATURATION,\n                                    m_renderData.pMonitor->m_sdrSaturation > 0 &&\n                                            m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?\n                                        m_renderData.pMonitor->m_sdrSaturation :\n                                        1.0f);\n            shader->setUniformFloat(SHADER_SDR_BRIGHTNESS,\n                                    m_renderData.pMonitor->m_sdrBrightness > 0 &&\n                                            m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?\n                                        m_renderData.pMonitor->m_sdrBrightness :\n                                        1.0f);\n        } else\n            shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE));\n\n        const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(MONITORBOX, *PBLEND ? HYPRUTILS_TRANSFORM_NORMAL : TRANSFORM);\n        shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n        shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST);\n        shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS);\n        shader->setUniformInt(SHADER_TEX, 0);\n\n        glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n        if (!damage.empty()) {\n            damage.forEachRect([this](const auto& RECT) {\n                scissor(&RECT, false /* this region is already transformed */);\n                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n            });\n        }\n\n        glBindVertexArray(0);\n        currentRenderToFB = PMIRRORSWAPFB;\n    }\n\n    // declare the draw func\n    auto drawPass = [&](WP<CShader> shader, ePreparedFragmentShader frag, CRegion* pDamage) {\n        if (currentRenderToFB == PMIRRORFB)\n            PMIRRORSWAPFB->bind();\n        else\n            PMIRRORFB->bind();\n\n        glActiveTexture(GL_TEXTURE0);\n\n        auto currentTex = currentRenderToFB->getTexture();\n\n        currentTex->bind();\n\n        currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\n        // prep two shaders\n        shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n        shader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a\n        if (frag == SH_FRAG_BLUR1) {\n            shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f));\n            shader->setUniformInt(SHADER_PASSES, BLUR_PASSES);\n            shader->setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY);\n            shader->setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS);\n        } else\n            shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f));\n        shader->setUniformInt(SHADER_TEX, 0);\n\n        glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n        if (!pDamage->empty()) {\n            pDamage->forEachRect([this](const auto& RECT) {\n                scissor(&RECT, false /* this region is already transformed */);\n                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n            });\n        }\n\n        glBindVertexArray(0);\n\n        if (currentRenderToFB != PMIRRORFB)\n            currentRenderToFB = PMIRRORFB;\n        else\n            currentRenderToFB = PMIRRORSWAPFB;\n    };\n\n    // draw the things.\n    // first draw is swap -> mirr\n    PMIRRORFB->bind();\n    PMIRRORSWAPFB->getTexture()->bind();\n\n    // damage region will be scaled, make a temp\n    CRegion tempDamage{damage};\n\n    // and draw\n    auto shader = useShader(getShaderVariant(SH_FRAG_BLUR1));\n    for (auto i = 1; i <= BLUR_PASSES; ++i) {\n        tempDamage = damage.copy().scale(1.f / (1 << i));\n        drawPass(shader, SH_FRAG_BLUR1, &tempDamage); // down\n    }\n\n    shader = useShader(getShaderVariant(SH_FRAG_BLUR2));\n    for (auto i = BLUR_PASSES - 1; i >= 0; --i) {\n        tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big\n        drawPass(shader, SH_FRAG_BLUR2, &tempDamage);     // up\n    }\n\n    // finalize the image\n    {\n        static auto PBLURNOISE      = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:noise\");\n        static auto PBLURBRIGHTNESS = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:brightness\");\n\n        if (currentRenderToFB == PMIRRORFB)\n            PMIRRORSWAPFB->bind();\n        else\n            PMIRRORFB->bind();\n\n        glActiveTexture(GL_TEXTURE0);\n\n        auto currentTex = currentRenderToFB->getTexture();\n\n        currentTex->bind();\n\n        currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\n        auto shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH));\n        shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n        shader->setUniformFloat(SHADER_NOISE, *PBLURNOISE);\n        shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS);\n\n        shader->setUniformInt(SHADER_TEX, 0);\n\n        glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n        if (!damage.empty()) {\n            damage.forEachRect([this](const auto& RECT) {\n                scissor(&RECT, false /* this region is already transformed */);\n                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n            });\n        }\n\n        glBindVertexArray(0);\n\n        if (currentRenderToFB != PMIRRORFB)\n            currentRenderToFB = PMIRRORFB;\n        else\n            currentRenderToFB = PMIRRORSWAPFB;\n    }\n\n    // finish\n    PMIRRORFB->getTexture()->unbind();\n\n    blend(BLENDBEFORE);\n\n    return currentRenderToFB;\n}\n\nvoid CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) {\n    static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>(\"decoration:blur:new_optimizations\");\n    static auto PBLURXRAY        = CConfigValue<Hyprlang::INT>(\"decoration:blur:xray\");\n    static auto PBLUR            = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n\n    if (!*PBLURNEWOPTIMIZE || !pMonitor->m_blurFBDirty || !*PBLUR)\n        return;\n\n    // ignore if solitary present, nothing to blur\n    if (!pMonitor->m_solitaryClient.expired())\n        return;\n\n    // check if we need to update the blur fb\n    // if there are no windows that would benefit from it,\n    // we will ignore that the blur FB is dirty.\n\n    auto windowShouldBeBlurred = [&](PHLWINDOW pWindow) -> bool {\n        if (!pWindow)\n            return false;\n\n        if (pWindow->m_ruleApplicator->noBlur().valueOrDefault())\n            return false;\n\n        if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall)\n            return true;\n\n        const auto  PSURFACE = pWindow->wlSurface()->resource();\n\n        const auto  PWORKSPACE = pWindow->m_workspace;\n        const float A          = pWindow->m_alpha->value() * pWindow->m_activeInactiveAlpha->value() * PWORKSPACE->m_alpha->value();\n\n        if (A >= 1.f) {\n            // if (PSURFACE->opaque)\n            //   return false;\n\n            CRegion        inverseOpaque;\n\n            pixman_box32_t surfbox = {0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y};\n            CRegion        opaqueRegion{PSURFACE->m_current.opaque};\n            inverseOpaque.set(opaqueRegion).invert(&surfbox).intersect(0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y);\n\n            if (inverseOpaque.empty())\n                return false;\n        }\n\n        return true;\n    };\n\n    bool hasWindows = false;\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->m_workspace == pMonitor->m_activeWorkspace && !w->isHidden() && w->m_isMapped && (!w->m_isFloating || *PBLURXRAY)) {\n\n            // check if window is valid\n            if (!windowShouldBeBlurred(w))\n                continue;\n\n            hasWindows = true;\n            break;\n        }\n    }\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        for (auto const& lsl : m->m_layerSurfaceLayers) {\n            for (auto const& ls : lsl) {\n                if (!ls->m_layerSurface || ls->m_ruleApplicator->xray().valueOrDefault() != 1)\n                    continue;\n\n                // if (ls->layerSurface->surface->opaque && ls->alpha->value() >= 1.f)\n                //     continue;\n\n                hasWindows = true;\n                break;\n            }\n        }\n    }\n\n    if (!hasWindows)\n        return;\n\n    g_pHyprRenderer->damageMonitor(pMonitor);\n    pMonitor->m_blurFBShouldRender = true;\n}\n\nvoid CHyprOpenGLImpl::renderTextureWithBlurInternal(SP<ITexture> tex, const CBox& box, const STextureRenderData& data) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    RASSERT(m_renderData.pMonitor, \"Tried to render texture with blur without begin()!\");\n\n    TRACY_GPU_ZONE(\"RenderTextureWithBlur\");\n\n    static auto PBLEND        = CConfigValue<Hyprlang::INT>(\"render:use_shader_blur_blend\");\n    const auto  NEEDS_STENCIL = data.discardMode != 0 && (!data.blockBlurOptimization || (data.discardMode & DISCARD_ALPHA));\n    if (!*PBLEND) {\n\n        if (NEEDS_STENCIL) {\n            scissor(nullptr); // allow the entire window and stencil to render\n            glClearStencil(0);\n            glClear(GL_STENCIL_BUFFER_BIT);\n\n            setCapStatus(GL_STENCIL_TEST, true);\n\n            glStencilFunc(GL_ALWAYS, 1, 0xFF);\n            glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);\n\n            glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);\n\n            renderTexture(tex, box,\n                          STextureRenderData{\n                              .damage                      = &g_pHyprRenderer->m_renderData.damage,\n                              .a                           = data.a,\n                              .round                       = data.round,\n                              .roundingPower               = data.roundingPower,\n                              .discardActive               = true,\n                              .allowCustomUV               = true,\n                              .wrapX                       = data.wrapX,\n                              .wrapY                       = data.wrapY,\n                              .discardMode                 = data.discardMode,\n                              .discardOpacity              = data.discardOpacity,\n                              .clipRegion                  = data.clipRegion,\n                              .currentLS                   = data.currentLS,\n                              .primarySurfaceUVTopLeft     = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft,\n                              .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight,\n                          }); // discard opaque and alpha < discardOpacity\n\n            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);\n\n            glStencilFunc(GL_EQUAL, 1, 0xFF);\n            glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);\n        }\n\n        // stencil done. Render everything.\n        CBox transformedBox = box;\n        transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                                 m_renderData.pMonitor->m_transformedSize.y);\n\n        CBox        monitorSpaceBox = {transformedBox.pos().x / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x,\n                                       transformedBox.pos().y / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y,\n                                       transformedBox.width / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x,\n                                       transformedBox.height / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y};\n\n        static auto PBLURIGNOREOPACITY = CConfigValue<Hyprlang::INT>(\"decoration:blur:ignore_opacity\");\n\n        g_pHyprRenderer->pushMonitorTransformEnabled(true);\n        bool renderModif = g_pHyprRenderer->m_renderData.renderModif.enabled;\n        if (!data.blockBlurOptimization)\n            g_pHyprRenderer->m_renderData.renderModif.enabled = false;\n\n        renderTextureInternal(data.blurredBG, box,\n                              STextureRenderData{\n                                  .damage         = data.damage,\n                                  .a              = (*PBLURIGNOREOPACITY ? data.blurA : data.a * data.blurA) * data.overallA,\n                                  .round          = data.round,\n                                  .roundingPower  = data.roundingPower,\n                                  .discardActive  = false,\n                                  .allowCustomUV  = true,\n                                  .noAA           = false,\n                                  .wrapX          = data.wrapX,\n                                  .wrapY          = data.wrapY,\n                                  .discardMode    = data.discardMode,\n                                  .discardOpacity = data.discardOpacity,\n                                  .clipRegion     = data.clipRegion,\n                                  .currentLS      = data.currentLS,\n\n                                  .primarySurfaceUVTopLeft     = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize,\n                                  .primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize,\n                              });\n\n        g_pHyprRenderer->m_renderData.renderModif.enabled = renderModif;\n        g_pHyprRenderer->popMonitorTransformEnabled();\n\n        if (NEEDS_STENCIL)\n            setCapStatus(GL_STENCIL_TEST, false);\n    }\n\n    // draw window\n    renderTextureInternal(tex, box,\n                          STextureRenderData{\n                              .blur           = *PBLEND,\n                              .blurredBG      = data.blurredBG,\n                              .damage         = data.damage,\n                              .a              = data.a * data.overallA,\n                              .round          = data.round,\n                              .roundingPower  = data.roundingPower,\n                              .discardActive  = *PBLEND && NEEDS_STENCIL,\n                              .allowCustomUV  = true,\n                              .allowDim       = true,\n                              .noAA           = false,\n                              .wrapX          = data.wrapX,\n                              .wrapY          = data.wrapY,\n                              .discardMode    = data.discardMode,\n                              .discardOpacity = data.discardOpacity,\n                              .clipRegion     = data.clipRegion,\n                              .currentLS      = data.currentLS,\n\n                              .primarySurfaceUVTopLeft     = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft,\n                              .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight,\n                          });\n\n    GLFB(g_pHyprRenderer->m_renderData.currentFB)->invalidate({GL_STENCIL_ATTACHMENT});\n    scissor(nullptr);\n}\n\nvoid CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& grad, SBorderRenderData data) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    RASSERT((box.width > 0 && box.height > 0), \"Tried to render rect with width/height < 0!\");\n    RASSERT(m_renderData.pMonitor, \"Tried to render rect without begin()!\");\n\n    TRACY_GPU_ZONE(\"RenderBorder\");\n\n    if (g_pHyprRenderer->m_renderData.damage.empty())\n        return;\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    if (data.borderSize < 1)\n        return;\n\n    int scaledBorderSize = std::round(data.borderSize * m_renderData.pMonitor->m_scale);\n    scaledBorderSize     = std::round(scaledBorderSize * g_pHyprRenderer->m_renderData.renderModif.combinedScale());\n\n    // adjust box\n    newBox.x -= scaledBorderSize;\n    newBox.y -= scaledBorderSize;\n    newBox.width += 2 * scaledBorderSize;\n    newBox.height += 2 * scaledBorderSize;\n\n    float       round = data.round + (data.round == 0 ? 0 : scaledBorderSize);\n\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);\n\n    const auto  BLEND = m_blend;\n    blend(true);\n\n    WP<CShader> shader;\n\n    const bool  IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present;\n    const bool  skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id();\n    if (!skipCM) {\n        shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD)));\n        passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION);\n    } else\n        shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING));\n\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n    shader->setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA);\n    shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4);\n    shader->setUniformFloat(SHADER_ANGLE, sc<int>(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0));\n    shader->setUniformFloat(SHADER_ALPHA, data.a);\n    shader->setUniformInt(SHADER_GRADIENT2_LENGTH, 0);\n\n    CBox transformedBox = newBox;\n    transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                             m_renderData.pMonitor->m_transformedSize.y);\n\n    const auto TOPLEFT  = Vector2D(transformedBox.x, transformedBox.y);\n    const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);\n\n    shader->setUniformFloat2(SHADER_TOP_LEFT, sc<float>(TOPLEFT.x), sc<float>(TOPLEFT.y));\n    shader->setUniformFloat2(SHADER_FULL_SIZE, sc<float>(FULLSIZE.x), sc<float>(FULLSIZE.y));\n    shader->setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc<float>(newBox.width), sc<float>(newBox.height));\n    shader->setUniformFloat(SHADER_RADIUS, round);\n    shader->setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound);\n    shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower);\n    shader->setUniformFloat(SHADER_THICK, scaledBorderSize);\n\n    glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n    // calculate the border's region, which we need to render over. No need to run the shader on\n    // things outside there\n    CRegion borderRegion = g_pHyprRenderer->m_renderData.damage.copy().intersect(newBox);\n    borderRegion.subtract(box.copy().expand(-scaledBorderSize - round));\n\n    if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0)\n        borderRegion.intersect(g_pHyprRenderer->m_renderData.clipBox);\n\n    if (!borderRegion.empty()) {\n        borderRegion.forEachRect([this](const auto& RECT) {\n            scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n        });\n    }\n\n    glBindVertexArray(0);\n\n    blend(BLEND);\n}\n\nvoid CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& grad1, const CGradientValueData& grad2, float lerp, SBorderRenderData data) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    RASSERT((box.width > 0 && box.height > 0), \"Tried to render rect with width/height < 0!\");\n    RASSERT(m_renderData.pMonitor, \"Tried to render rect without begin()!\");\n\n    TRACY_GPU_ZONE(\"RenderBorder2\");\n\n    if (g_pHyprRenderer->m_renderData.damage.empty())\n        return;\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    if (data.borderSize < 1)\n        return;\n\n    int scaledBorderSize = std::round(data.borderSize * m_renderData.pMonitor->m_scale);\n    scaledBorderSize     = std::round(scaledBorderSize * g_pHyprRenderer->m_renderData.renderModif.combinedScale());\n\n    // adjust box\n    newBox.x -= scaledBorderSize;\n    newBox.y -= scaledBorderSize;\n    newBox.width += 2 * scaledBorderSize;\n    newBox.height += 2 * scaledBorderSize;\n\n    float       round = data.round + (data.round == 0 ? 0 : scaledBorderSize);\n\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);\n\n    const auto  BLEND = m_blend;\n    blend(true);\n\n    WP<CShader> shader;\n    const bool  IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present;\n    const bool  skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id();\n    if (!skipCM) {\n        shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD)));\n        passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION);\n    } else\n        shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING));\n\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n    shader->setUniform4fv(SHADER_GRADIENT, grad1.m_colorsOkLabA.size() / 4, grad1.m_colorsOkLabA);\n    shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad1.m_colorsOkLabA.size() / 4);\n    shader->setUniformFloat(SHADER_ANGLE, sc<int>(grad1.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0));\n    if (!grad2.m_colorsOkLabA.empty())\n        shader->setUniform4fv(SHADER_GRADIENT2, grad2.m_colorsOkLabA.size() / 4, grad2.m_colorsOkLabA);\n    shader->setUniformInt(SHADER_GRADIENT2_LENGTH, grad2.m_colorsOkLabA.size() / 4);\n    shader->setUniformFloat(SHADER_ANGLE2, sc<int>(grad2.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0));\n    shader->setUniformFloat(SHADER_ALPHA, data.a);\n    shader->setUniformFloat(SHADER_GRADIENT_LERP, lerp);\n\n    CBox transformedBox = newBox;\n    transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                             m_renderData.pMonitor->m_transformedSize.y);\n\n    const auto TOPLEFT  = Vector2D(transformedBox.x, transformedBox.y);\n    const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);\n\n    shader->setUniformFloat2(SHADER_TOP_LEFT, sc<float>(TOPLEFT.x), sc<float>(TOPLEFT.y));\n    shader->setUniformFloat2(SHADER_FULL_SIZE, sc<float>(FULLSIZE.x), sc<float>(FULLSIZE.y));\n    shader->setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc<float>(newBox.width), sc<float>(newBox.height));\n    shader->setUniformFloat(SHADER_RADIUS, round);\n    shader->setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound);\n    shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower);\n    shader->setUniformFloat(SHADER_THICK, scaledBorderSize);\n\n    glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n    // calculate the border's region, which we need to render over. No need to run the shader on\n    // things outside there\n    CRegion borderRegion = g_pHyprRenderer->m_renderData.damage.copy().intersect(newBox);\n    borderRegion.subtract(box.copy().expand(-scaledBorderSize - round));\n\n    if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0)\n        borderRegion.intersect(g_pHyprRenderer->m_renderData.clipBox);\n\n    if (!borderRegion.empty()) {\n        borderRegion.forEachRect([this](const auto& RECT) {\n            scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n        });\n    }\n\n    glBindVertexArray(0);\n    blend(BLEND);\n}\n\nvoid CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roundingPower, int range, const CHyprColor& color, float a) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    RASSERT(m_renderData.pMonitor, \"Tried to render shadow without begin()!\");\n    RASSERT((box.width > 0 && box.height > 0), \"Tried to render shadow with width/height < 0!\");\n\n    if (g_pHyprRenderer->m_renderData.damage.empty())\n        return;\n\n    TRACY_GPU_ZONE(\"RenderShadow\");\n\n    CBox newBox = box;\n    g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);\n\n    static auto PSHADOWPOWER = CConfigValue<Hyprlang::INT>(\"decoration:shadow:render_power\");\n\n    const auto  SHADOWPOWER = std::clamp(sc<int>(*PSHADOWPOWER), 1, 4);\n\n    const auto  col = color;\n\n    const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);\n\n    blend(true);\n\n    const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present;\n    const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id();\n    auto       shader = useShader(getShaderVariant(SH_FRAG_SHADOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD)));\n    if (!skipCM)\n        passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION);\n\n    shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());\n    shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a);\n\n    const auto TOPLEFT     = Vector2D(range + round, range + round);\n    const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round));\n    const auto FULLSIZE    = Vector2D(newBox.width, newBox.height);\n\n    // Rounded corners\n    shader->setUniformFloat2(SHADER_TOP_LEFT, sc<float>(TOPLEFT.x), sc<float>(TOPLEFT.y));\n    shader->setUniformFloat2(SHADER_BOTTOM_RIGHT, sc<float>(BOTTOMRIGHT.x), sc<float>(BOTTOMRIGHT.y));\n    shader->setUniformFloat2(SHADER_FULL_SIZE, sc<float>(FULLSIZE.x), sc<float>(FULLSIZE.y));\n    shader->setUniformFloat(SHADER_RADIUS, range + round);\n    shader->setUniformFloat(SHADER_ROUNDING_POWER, roundingPower);\n    shader->setUniformFloat(SHADER_RANGE, range);\n    shader->setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER);\n\n    glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));\n\n    if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) {\n        CRegion damageClip{g_pHyprRenderer->m_renderData.clipBox.x, g_pHyprRenderer->m_renderData.clipBox.y, g_pHyprRenderer->m_renderData.clipBox.width,\n                           g_pHyprRenderer->m_renderData.clipBox.height};\n        damageClip.intersect(g_pHyprRenderer->m_renderData.damage);\n\n        if (!damageClip.empty()) {\n            damageClip.forEachRect([this](const auto& RECT) {\n                scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n            });\n        }\n    } else {\n        g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) {\n            scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);\n            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n        });\n    }\n\n    glBindVertexArray(0);\n}\n\nvoid CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) {\n    auto& m_renderData = g_pHyprRenderer->m_renderData;\n    if (!g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->isAllocated())\n        g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y,\n                                                                         m_renderData.pMonitor->m_output->state->state().drmFormat);\n\n    g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->bind();\n\n    blend(false);\n\n    renderTexture(g_pHyprRenderer->m_renderData.currentFB->getTexture(), box,\n                  STextureRenderData{\n                      .damage        = &g_pHyprRenderer->m_renderData.finalDamage,\n                      .a             = 1.F,\n                      .round         = 0,\n                      .discardActive = false,\n                      .allowCustomUV = false,\n                      .cmBackToSRGB  = true,\n                  });\n\n    blend(true);\n\n    g_pHyprRenderer->m_renderData.currentFB->bind();\n}\n\nWP<CShader> CHyprOpenGLImpl::useShader(WP<CShader> prog) {\n    if (m_currentProgram == prog->program())\n        return prog;\n\n    glUseProgram(prog->program());\n    m_currentProgram = prog->program();\n\n    return prog;\n}\n\nvoid CHyprOpenGLImpl::destroyMonitorResources(PHLMONITORREF pMonitor) {\n    makeEGLCurrent();\n\n    if (!g_pHyprOpenGL)\n        return;\n\n    auto TEXIT = g_pHyprOpenGL->m_monitorBGFBs.find(pMonitor);\n    if (TEXIT != g_pHyprOpenGL->m_monitorBGFBs.end()) {\n        TEXIT->second.reset();\n        g_pHyprOpenGL->m_monitorBGFBs.erase(TEXIT);\n    }\n\n    if (pMonitor)\n        Log::logger->log(Log::DEBUG, \"Monitor {} -> destroyed all render data\", pMonitor->m_name);\n}\n\nvoid CHyprOpenGLImpl::renderOffToMain(IFramebuffer* off) {\n    CBox monbox = {0, 0, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.x, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.y};\n    renderTexturePrimitive(off->getTexture(), monbox);\n}\n\nvoid CHyprOpenGLImpl::setViewport(GLint x, GLint y, GLsizei width, GLsizei height) {\n    if (m_lastViewport.x == x && m_lastViewport.y == y && m_lastViewport.width == width && m_lastViewport.height == height)\n        return;\n\n    glViewport(x, y, width, height);\n    m_lastViewport = {.x = x, .y = y, .width = width, .height = height};\n}\n\nvoid CHyprOpenGLImpl::setCapStatus(int cap, bool status) {\n    const auto getCapIndex = [cap]() {\n        switch (cap) {\n            case GL_BLEND: return CAP_STATUS_BLEND;\n            case GL_SCISSOR_TEST: return CAP_STATUS_SCISSOR_TEST;\n            case GL_STENCIL_TEST: return CAP_STATUS_STENCIL_TEST;\n            default: return CAP_STATUS_END;\n        }\n    };\n\n    auto idx = getCapIndex();\n\n    if (idx == CAP_STATUS_END) {\n        if (status)\n            GLCALL(glEnable(cap))\n        else\n            GLCALL(glDisable(cap));\n\n        return;\n    }\n\n    if (m_capStatus[idx] == status)\n        return;\n\n    if (status) {\n        m_capStatus[idx] = status;\n        GLCALL(glEnable(cap));\n    } else {\n        m_capStatus[idx] = status;\n        GLCALL(glDisable(cap));\n    }\n}\n\nstd::vector<uint64_t> CHyprOpenGLImpl::getDRMFormatModifiers(DRMFormat drmFormat) {\n    SDRMFormat format;\n\n    for (const auto& fmt : m_drmFormats) {\n        if (fmt.drmFormat == drmFormat) {\n            format = fmt;\n            break;\n        }\n    }\n\n    return format.modifiers;\n}\n\nbool CHyprOpenGLImpl::explicitSyncSupported() {\n    return m_exts.EGL_ANDROID_native_fence_sync_ext;\n}\n\nWP<CShader> CHyprOpenGLImpl::getShaderVariant(ePreparedFragmentShader frag, ShaderFeatureFlags features) {\n    if (!m_shaders->fragVariants[frag].contains(features)) {\n        auto shader = makeShared<CShader>();\n\n        Log::logger->log(Log::INFO, \"compiling feature set {} for {}\", features, FRAG_SHADERS[frag]);\n\n        const auto fragSrc = g_pShaderLoader->getVariantSource(frag, features);\n\n        if (!shader->createProgram(m_shaders->TEXVERTSRC, fragSrc, true, true))\n            Log::logger->log(Log::ERR, \"shader features {} failed for {}\", features, FRAG_SHADERS[frag]);\n\n        m_shaders->fragVariants[frag][features] = shader;\n        return shader;\n    }\n\n    ASSERT(m_shaders->fragVariants[frag][features]);\n    return m_shaders->fragVariants[frag][features];\n}\n\nstd::vector<SDRMFormat> CHyprOpenGLImpl::getDRMFormats() {\n    return m_drmFormats;\n}\n\nvoid SRenderModifData::applyToBox(CBox& box) {\n    if (!enabled)\n        return;\n\n    for (auto const& [type, val] : modifs) {\n        try {\n            switch (type) {\n                case RMOD_TYPE_SCALE: box.scale(std::any_cast<float>(val)); break;\n                case RMOD_TYPE_SCALECENTER: box.scaleFromCenter(std::any_cast<float>(val)); break;\n                case RMOD_TYPE_TRANSLATE: box.translate(std::any_cast<Vector2D>(val)); break;\n                case RMOD_TYPE_ROTATE: box.rot += std::any_cast<float>(val); break;\n                case RMOD_TYPE_ROTATECENTER: {\n                    const auto   THETA = std::any_cast<float>(val);\n                    const double COS   = std::cos(THETA);\n                    const double SIN   = std::sin(THETA);\n                    box.rot += THETA;\n                    const auto OLDPOS = box.pos();\n                    box.x             = OLDPOS.x * COS - OLDPOS.y * SIN;\n                    box.y             = OLDPOS.y * COS + OLDPOS.x * SIN;\n                }\n            }\n        } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, \"BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToBox!\"); }\n    }\n}\n\nvoid SRenderModifData::applyToRegion(CRegion& rg) {\n    if (!enabled)\n        return;\n\n    for (auto const& [type, val] : modifs) {\n        try {\n            switch (type) {\n                case RMOD_TYPE_SCALE: rg.scale(std::any_cast<float>(val)); break;\n                case RMOD_TYPE_SCALECENTER: rg.scale(std::any_cast<float>(val)); break;\n                case RMOD_TYPE_TRANSLATE: rg.translate(std::any_cast<Vector2D>(val)); break;\n                case RMOD_TYPE_ROTATE: /* TODO */\n                case RMOD_TYPE_ROTATECENTER: break;\n            }\n        } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, \"BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToRegion!\"); }\n    }\n}\n\nfloat SRenderModifData::combinedScale() {\n    if (!enabled)\n        return 1;\n\n    float scale = 1.f;\n    for (auto const& [type, val] : modifs) {\n        try {\n            switch (type) {\n                case RMOD_TYPE_SCALE: scale *= std::any_cast<float>(val); break;\n                case RMOD_TYPE_SCALECENTER:\n                case RMOD_TYPE_TRANSLATE:\n                case RMOD_TYPE_ROTATE:\n                case RMOD_TYPE_ROTATECENTER: break;\n            }\n        } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, \"BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::combinedScale!\"); }\n    }\n    return scale;\n}\n\nUP<CEGLSync> CEGLSync::create() {\n    RASSERT(g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext, \"Tried to create an EGL sync when syncs are not supported on the gpu\");\n\n    EGLSyncKHR sync = g_pHyprOpenGL->m_proc.eglCreateSyncKHR(g_pHyprOpenGL->m_eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);\n\n    if UNLIKELY (sync == EGL_NO_SYNC_KHR) {\n        Log::logger->log(Log::ERR, \"eglCreateSyncKHR failed\");\n        return nullptr;\n    }\n\n    // we need to flush otherwise we might not get a valid fd\n    glFlush();\n\n    int fd = g_pHyprOpenGL->m_proc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_eglDisplay, sync);\n    if UNLIKELY (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {\n        Log::logger->log(Log::ERR, \"eglDupNativeFenceFDANDROID failed\");\n        return nullptr;\n    }\n\n    UP<CEGLSync> eglSync(new CEGLSync);\n    eglSync->m_fd    = CFileDescriptor(fd);\n    eglSync->m_sync  = sync;\n    eglSync->m_valid = true;\n\n    return eglSync;\n}\n\nCEGLSync::~CEGLSync() {\n    if UNLIKELY (m_sync == EGL_NO_SYNC_KHR)\n        return;\n\n    if UNLIKELY (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE)\n        Log::logger->log(Log::ERR, \"eglDestroySyncKHR failed\");\n}\n\nCFileDescriptor& CEGLSync::fd() {\n    return m_fd;\n}\n\nCFileDescriptor&& CEGLSync::takeFd() {\n    return std::move(m_fd);\n}\n\nbool CEGLSync::isValid() {\n    return m_valid && m_sync != EGL_NO_SYNC_KHR && m_fd.isValid();\n}\n"
  },
  {
    "path": "src/render/OpenGL.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"../helpers/Color.hpp\"\n#include \"../helpers/time/Timer.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../helpers/Format.hpp\"\n#include \"../helpers/sync/SyncTimeline.hpp\"\n#include <GLES3/gl32.h>\n#include <cstdint>\n#include <list>\n#include <string>\n#include <stack>\n#include <map>\n\n#include <cairo/cairo.h>\n\n#include \"Shader.hpp\"\n#include \"Texture.hpp\"\n#include \"Framebuffer.hpp\"\n#include \"Renderbuffer.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"pass/Pass.hpp\"\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <GLES2/gl2ext.h>\n#include <aquamarine/buffer/Buffer.hpp>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <hyprgraphics/resource/resources/ImageResource.hpp>\n\n#include \"../debug/TracyDefines.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"ShaderLoader.hpp\"\n#include \"gl/GLFramebuffer.hpp\"\n#include \"gl/GLRenderbuffer.hpp\"\n#include \"pass/TexPassElement.hpp\"\n\n#define GLFB(ifb) dc<CGLFramebuffer*>(ifb.get())\n\nstruct gbm_device;\nclass IHyprRenderer;\n\nstruct SVertex {\n    float x, y; // position\n    float u, v; // uv\n};\n\nconstexpr std::array<SVertex, 4> fullVerts = {{\n    {0.0f, 0.0f, 0.0f, 0.0f}, // top-left\n    {0.0f, 1.0f, 0.0f, 1.0f}, // bottom-left\n    {1.0f, 0.0f, 1.0f, 0.0f}, // top-right\n    {1.0f, 1.0f, 1.0f, 1.0f}, // bottom-right\n}};\n\ninline const float               fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};\n\nstruct SRenderModifData {\n    enum eRenderModifType : uint8_t {\n        RMOD_TYPE_SCALE,        /* scale by a float */\n        RMOD_TYPE_SCALECENTER,  /* scale by a float from the center */\n        RMOD_TYPE_TRANSLATE,    /* translate by a Vector2D */\n        RMOD_TYPE_ROTATE,       /* rotate by a float in rad from top left */\n        RMOD_TYPE_ROTATECENTER, /* rotate by a float in rad from center */\n    };\n\n    std::vector<std::pair<eRenderModifType, std::any>> modifs;\n\n    void                                               applyToBox(CBox& box);\n    void                                               applyToRegion(CRegion& rg);\n    float                                              combinedScale();\n\n    bool                                               enabled = true;\n};\n\nenum eMonitorRenderFBs : uint8_t {\n    FB_MONITOR_RENDER_MAIN    = 0,\n    FB_MONITOR_RENDER_CURRENT = 1,\n    FB_MONITOR_RENDER_OUT     = 2,\n};\n\nenum eMonitorExtraRenderFBs : uint8_t {\n    FB_MONITOR_RENDER_EXTRA_OFFLOAD = 0,\n    FB_MONITOR_RENDER_EXTRA_MIRROR,\n    FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP,\n    FB_MONITOR_RENDER_EXTRA_OFF_MAIN,\n    FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR,\n    FB_MONITOR_RENDER_EXTRA_BLUR,\n};\n\nstruct SFragShaderDesc {\n    Render::ePreparedFragmentShader id;\n    const char*                     file;\n};\n\nstruct SPreparedShaders {\n    // SPreparedShaders() {\n    //     for (auto& f : frag) {\n    //         f = makeShared<CShader>();\n    //     }\n    // }\n\n    std::string TEXVERTSRC;\n    std::string TEXVERTSRC320;\n    // std::array<SP<CShader>, SH_FRAG_LAST> frag;\n    // std::map<uint8_t, SP<CShader>>        fragVariants;\n    std::array<std::map<Render::ShaderFeatureFlags, SP<CShader>>, Render::SH_FRAG_LAST> fragVariants;\n};\n\nstruct SCurrentRenderData {\n    PHLMONITORREF          pMonitor;\n    Mat3x3                 projection;\n    Mat3x3                 savedProjection;\n    Mat3x3                 monitorProjection;\n\n    SP<IFramebuffer>       currentFB = nullptr; // current rendering to\n    SP<IFramebuffer>       mainFB    = nullptr; // main to render to\n    SP<IFramebuffer>       outFB     = nullptr; // out to render to (if offloaded, etc)\n\n    CRegion                damage;\n    CRegion                finalDamage; // damage used for funal off -> main\n\n    SRenderModifData       renderModif;\n    float                  mouseZoomFactor    = 1.f;\n    bool                   mouseZoomUseMouse  = true; // true by default\n    bool                   useNearestNeighbor = false;\n    bool                   blockScreenShader  = false;\n    bool                   simplePass         = false;\n    bool                   transformDamage    = true;\n    bool                   noSimplify         = false;\n\n    Vector2D               primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n    Vector2D               primarySurfaceUVBottomRight = Vector2D(-1, -1);\n\n    CBox                   clipBox = {}; // scaled coordinates\n    CRegion                clipRegion;\n\n    uint32_t               discardMode    = DISCARD_OPAQUE;\n    float                  discardOpacity = 0.f;\n\n    PHLLSREF               currentLS;\n    PHLWINDOWREF           currentWindow;\n    WP<CWLSurfaceResource> surface;\n};\n\nclass CEGLSync {\n  public:\n    static UP<CEGLSync> create();\n\n    ~CEGLSync();\n\n    Hyprutils::OS::CFileDescriptor&  fd();\n    Hyprutils::OS::CFileDescriptor&& takeFd();\n    bool                             isValid();\n\n  private:\n    CEGLSync() = default;\n\n    Hyprutils::OS::CFileDescriptor m_fd;\n    EGLSyncKHR                     m_sync  = EGL_NO_SYNC_KHR;\n    bool                           m_valid = false;\n\n    friend class CHyprOpenGLImpl;\n};\n\nclass CGradientValueData;\n\nclass CHyprOpenGLImpl {\n  public:\n    CHyprOpenGLImpl();\n    ~CHyprOpenGLImpl();\n\n    struct SRectRenderData {\n        const CRegion* damage        = nullptr;\n        int            round         = 0;\n        float          roundingPower = 2.F;\n        bool           blur          = false;\n        float          blurA         = 1.F;\n        bool           xray          = false;\n    };\n\n    struct STextureRenderData {\n        bool                   blur  = false;\n        float                  blurA = 1.F, overallA = 1.F;\n        bool                   blockBlurOptimization = false;\n        SP<ITexture>           blurredBG;\n\n        const CRegion*         damage        = nullptr;\n        SP<CWLSurfaceResource> surface       = nullptr;\n        float                  a             = 1.F;\n        int                    round         = 0;\n        float                  roundingPower = 2.F;\n        bool                   discardActive = false;\n        bool                   allowCustomUV = false;\n        bool                   allowDim      = true;\n        bool                   noAA          = false; // unused\n        GLenum                 wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE;\n        bool                   cmBackToSRGB   = false;\n        bool                   finalMonitorCM = false;\n        SP<CMonitor>           cmBackToSRGBSource;\n\n        uint32_t               discardMode    = DISCARD_OPAQUE;\n        float                  discardOpacity = 0.f;\n\n        CRegion                clipRegion;\n        PHLLSREF               currentLS;\n\n        Vector2D               primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n        Vector2D               primarySurfaceUVBottomRight = Vector2D(-1, -1);\n    };\n\n    struct SBorderRenderData {\n        int   round         = 0;\n        float roundingPower = 2.F;\n        int   borderSize    = 1;\n        float a             = 1.0;\n        int   outerRound    = -1; /* use round */\n    };\n\n    void                                      makeEGLCurrent();\n    void                                      begin(PHLMONITOR, const CRegion& damage, SP<IFramebuffer> fb = nullptr, std::optional<CRegion> finalDamage = {});\n    void                                      beginSimple(PHLMONITOR, const CRegion& damage, SP<IRenderbuffer> rb = nullptr, SP<IFramebuffer> fb = nullptr);\n    void                                      end();\n\n    void                                      renderRect(const CBox&, const CHyprColor&, SRectRenderData data);\n    void                                      renderTexture(SP<ITexture>, const CBox&, STextureRenderData data);\n    void                                      renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0);\n    void                                      renderBorder(const CBox&, const CGradientValueData&, SBorderRenderData data);\n    void                                      renderBorder(const CBox&, const CGradientValueData&, const CGradientValueData&, float lerp, SBorderRenderData data);\n    void                                      renderTextureMatte(SP<ITexture> tex, const CBox& pBox, SP<IFramebuffer> matte);\n    void                                      renderTexturePrimitive(SP<ITexture> tex, const CBox& box);\n\n    void                                      setViewport(GLint x, GLint y, GLsizei width, GLsizei height);\n    void                                      setCapStatus(int cap, bool status);\n\n    void                                      blend(bool enabled);\n\n    void                                      clear(const CHyprColor&);\n    void                                      scissor(const CBox&, bool transform = true);\n    void                                      scissor(const pixman_box32*, bool transform = true);\n    void                                      scissor(const int x, const int y, const int w, const int h, bool transform = true);\n\n    void                                      destroyMonitorResources(PHLMONITORREF);\n\n    void                                      preRender(PHLMONITOR);\n\n    void                                      saveBufferForMirror(const CBox&);\n\n    void                                      applyScreenShader(const std::string& path);\n\n    void                                      renderOffToMain(IFramebuffer* off);\n\n    std::vector<SDRMFormat>                   getDRMFormats();\n    std::vector<uint64_t>                     getDRMFormatModifiers(DRMFormat format);\n    EGLImageKHR                               createEGLImage(const Aquamarine::SDMABUFAttrs& attrs);\n\n    bool                                      initShaders(const std::string& path = \"\");\n\n    WP<CShader>                               useShader(WP<CShader> prog);\n\n    bool                                      explicitSyncSupported();\n    WP<CShader>                               getShaderVariant(Render::ePreparedFragmentShader frag, Render::ShaderFeatureFlags features = 0);\n\n    bool                                      m_shadersInitialized = false;\n    SP<SPreparedShaders>                      m_shaders;\n\n    Hyprutils::OS::CFileDescriptor            m_gbmFD;\n    gbm_device*                               m_gbmDevice  = nullptr;\n    EGLContext                                m_eglContext = nullptr;\n    EGLDisplay                                m_eglDisplay = nullptr;\n    EGLDeviceEXT                              m_eglDevice  = nullptr;\n\n    std::map<PHLMONITORREF, SP<IFramebuffer>> m_monitorBGFBs;\n\n    struct {\n        PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr;\n        PFNGLEGLIMAGETARGETTEXTURE2DOESPROC           glEGLImageTargetTexture2DOES           = nullptr;\n        PFNEGLCREATEIMAGEKHRPROC                      eglCreateImageKHR                      = nullptr;\n        PFNEGLDESTROYIMAGEKHRPROC                     eglDestroyImageKHR                     = nullptr;\n        PFNEGLQUERYDMABUFFORMATSEXTPROC               eglQueryDmaBufFormatsEXT               = nullptr;\n        PFNEGLQUERYDMABUFMODIFIERSEXTPROC             eglQueryDmaBufModifiersEXT             = nullptr;\n        PFNEGLGETPLATFORMDISPLAYEXTPROC               eglGetPlatformDisplayEXT               = nullptr;\n        PFNEGLDEBUGMESSAGECONTROLKHRPROC              eglDebugMessageControlKHR              = nullptr;\n        PFNEGLQUERYDEVICESEXTPROC                     eglQueryDevicesEXT                     = nullptr;\n        PFNEGLQUERYDEVICESTRINGEXTPROC                eglQueryDeviceStringEXT                = nullptr;\n        PFNEGLQUERYDISPLAYATTRIBEXTPROC               eglQueryDisplayAttribEXT               = nullptr;\n        PFNEGLCREATESYNCKHRPROC                       eglCreateSyncKHR                       = nullptr;\n        PFNEGLDESTROYSYNCKHRPROC                      eglDestroySyncKHR                      = nullptr;\n        PFNEGLDUPNATIVEFENCEFDANDROIDPROC             eglDupNativeFenceFDANDROID             = nullptr;\n        PFNEGLWAITSYNCKHRPROC                         eglWaitSyncKHR                         = nullptr;\n    } m_proc;\n\n    struct {\n        bool EXT_read_format_bgra               = false;\n        bool EXT_image_dma_buf_import           = false;\n        bool EXT_image_dma_buf_import_modifiers = false;\n        bool KHR_context_flush_control          = false;\n        bool KHR_display_reference              = false;\n        bool IMG_context_priority               = false;\n        bool EXT_create_context_robustness      = false;\n        bool EGL_ANDROID_native_fence_sync_ext  = false;\n    } m_exts;\n\n    enum eEGLContextVersion : uint8_t {\n        EGL_CONTEXT_GLES_2_0 = 0,\n        EGL_CONTEXT_GLES_3_0,\n        EGL_CONTEXT_GLES_3_2,\n    };\n\n    eEGLContextVersion m_eglContextVersion = EGL_CONTEXT_GLES_3_2;\n\n    enum eCachedCapStatus : uint8_t {\n        CAP_STATUS_BLEND = 0,\n        CAP_STATUS_SCISSOR_TEST,\n        CAP_STATUS_STENCIL_TEST,\n        CAP_STATUS_END\n    };\n\n  private:\n    struct {\n        GLint   x      = 0;\n        GLint   y      = 0;\n        GLsizei width  = 0;\n        GLsizei height = 0;\n    } m_lastViewport;\n\n    std::array<bool, CAP_STATUS_END> m_capStatus = {};\n\n    std::vector<SDRMFormat>          m_drmFormats;\n    bool                             m_hasModifiers = false;\n\n    int                              m_drmFD = -1;\n    std::string                      m_extensions;\n\n    bool                             m_fakeFrame            = false;\n    bool                             m_applyFinalShader     = false;\n    bool                             m_blend                = false;\n    bool                             m_offloadedFramebuffer = false;\n    bool                             m_cmSupported          = true;\n\n    SP<CShader>                      m_finalScreenShader;\n    GLuint                           m_currentProgram;\n\n    void                             initDRMFormats();\n    void                             initEGL(bool gbm);\n    EGLDeviceEXT                     eglDeviceFromDRMFD(int drmFD);\n\n    // for the final shader\n    std::array<CTimer, POINTER_PRESSED_HISTORY_LENGTH>   m_pressedHistoryTimers    = {};\n    std::array<Vector2D, POINTER_PRESSED_HISTORY_LENGTH> m_pressedHistoryPositions = {};\n    GLint                                                m_pressedHistoryKilled    = 0;\n    GLint                                                m_pressedHistoryTouched   = 0;\n\n    //\n    std::optional<std::vector<uint64_t>> getModsForFormat(EGLint format);\n\n    // returns the out FB, can be either Mirror or MirrorSwap\n    SP<IFramebuffer> blurFramebufferWithDamage(float a, CRegion* damage, CGLFramebuffer& source);\n\n    void             passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,\n                                    bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1);\n    void             passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription);\n    void             renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);\n    void             renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);\n    void             renderRectWithDamageInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);\n    WP<CShader>      renderToOutputInternal();\n    WP<CShader>      renderToFBInternal(const STextureRenderData& data, eTextureType texType, const CBox& newBox);\n    void             renderTextureInternal(SP<ITexture>, const CBox&, const STextureRenderData& data);\n    void             renderTextureWithBlurInternal(SP<ITexture>, const CBox&, const STextureRenderData& data);\n\n    friend class IHyprRenderer;\n    friend class CHyprGLRenderer;\n    friend class CTexPassElement;\n    friend class CPreBlurElement;\n    friend class CSurfacePassElement;\n};\n\ninline UP<CHyprOpenGLImpl> g_pHyprOpenGL;\n"
  },
  {
    "path": "src/render/Renderbuffer.cpp",
    "content": "#include \"Renderbuffer.hpp\"\n#include \"Framebuffer.hpp\"\n#include \"Renderer.hpp\"\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/signal/Listener.hpp>\n#include <hyprutils/signal/Signal.hpp>\n\n#include <dlfcn.h>\n\nIRenderbuffer::IRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t format) : m_hlBuffer(buffer) {\n    m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); });\n}\n\nbool IRenderbuffer::good() {\n    return m_good;\n}\n\nSP<IFramebuffer> IRenderbuffer::getFB() {\n    return m_framebuffer;\n}\n"
  },
  {
    "path": "src/render/Renderbuffer.hpp",
    "content": "#pragma once\n\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"Framebuffer.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n\nclass IRenderbuffer {\n  public:\n    IRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t format);\n    virtual ~IRenderbuffer() = default;\n\n    bool                    good();\n    SP<IFramebuffer>        getFB();\n\n    virtual void            bind()   = 0;\n    virtual void            unbind() = 0;\n\n    WP<Aquamarine::IBuffer> m_hlBuffer;\n\n  protected:\n    SP<IFramebuffer> m_framebuffer;\n    bool             m_good = false;\n\n    struct {\n        CHyprSignalListener destroyBuffer;\n    } m_listeners;\n};\n"
  },
  {
    "path": "src/render/Renderer.cpp",
    "content": "#include \"Renderer.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include <algorithm>\n#include <aquamarine/output/Output.hpp>\n#include <cmath>\n#include <filesystem>\n#include \"../config/ConfigValue.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"../managers/CursorManager.hpp\"\n#include \"../managers/PointerManager.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include \"../managers/animation/AnimationManager.hpp\"\n#include \"../desktop/view/Window.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"../desktop/view/GlobalViewMethods.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\n#include \"../protocols/SessionLock.hpp\"\n#include \"../protocols/LayerShell.hpp\"\n#include \"../protocols/XDGShell.hpp\"\n#include \"../protocols/PresentationTime.hpp\"\n#include \"../protocols/core/DataDevice.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../protocols/DRMSyncobj.hpp\"\n#include \"../protocols/LinuxDMABUF.hpp\"\n#include \"../helpers/sync/SyncTimeline.hpp\"\n#include \"../hyprerror/HyprError.hpp\"\n#include \"../debug/HyprDebugOverlay.hpp\"\n#include \"../debug/HyprNotificationOverlay.hpp\"\n#include \"../layout/LayoutManager.hpp\"\n#include \"../layout/space/Space.hpp\"\n#include \"../i18n/Engine.hpp\"\n#include \"../desktop/DesktopTypes.hpp\"\n#include \"../event/EventBus.hpp\"\n#include \"../helpers/CursorShapes.hpp\"\n#include \"../helpers/MainLoopExecutor.hpp\"\n#include \"../helpers/Monitor.hpp\"\n#include \"macros.hpp\"\n#include \"../managers/screenshare/ScreenshareManager.hpp\"\n#include \"pass/TexPassElement.hpp\"\n#include \"pass/ClearPassElement.hpp\"\n#include \"pass/RectPassElement.hpp\"\n#include \"pass/RendererHintsPassElement.hpp\"\n#include \"pass/SurfacePassElement.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../protocols/ColorManagement.hpp\"\n#include \"../protocols/types/ContentType.hpp\"\n#include \"../helpers/MiscFunctions.hpp\"\n#include \"AsyncResourceGatherer.hpp\"\n#include \"Framebuffer.hpp\"\n#include \"OpenGL.hpp\"\n#include \"Texture.hpp\"\n#include \"pass/BorderPassElement.hpp\"\n#include \"pass/PreBlurElement.hpp\"\n#include <hyprutils/math/Mat3x3.hpp>\n#include <hyprutils/math/Region.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <optional>\n#include <pango/pangocairo.h>\n\n#include <hyprutils/utils/ScopeGuard.hpp>\n#include <random>\nusing namespace Hyprutils::Utils;\nusing namespace Hyprutils::OS;\nusing enum NContentType::eContentType;\nusing namespace NColorManagement;\n\nextern \"C\" {\n#include <xf86drm.h>\n}\n\nstatic int cursorTicker(void* data) {\n    g_pHyprRenderer->ensureCursorRenderingMode();\n    wl_event_source_timer_update(g_pHyprRenderer->m_cursorTicker, 500);\n    return 0;\n}\n\nIHyprRenderer::IHyprRenderer() {\n    m_globalTimer.reset();\n    pushMonitorTransformEnabled(false);\n\n    if (g_pCompositor->m_aqBackend->hasSession()) {\n        size_t drmDevices = 0;\n        for (auto const& dev : g_pCompositor->m_aqBackend->session->sessionDevices) {\n            const auto DRMV = drmGetVersion(dev->fd);\n            if (!DRMV)\n                continue;\n            drmDevices++;\n            std::string name = std::string{DRMV->name, DRMV->name_len};\n            std::ranges::transform(name, name.begin(), tolower);\n\n            if (name.contains(\"nvidia\"))\n                m_nvidia = true;\n            else if (name.contains(\"i915\"))\n                m_intel = true;\n            else if (name.contains(\"softpipe\") || name.contains(\"Software Rasterizer\") || name.contains(\"llvmpipe\"))\n                m_software = true;\n\n            Log::logger->log(Log::DEBUG, \"DRM driver information: {} v{}.{}.{} from {} description {}\", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel,\n                             std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len});\n\n            drmFreeVersion(DRMV);\n        }\n        m_mgpu = drmDevices > 1;\n    } else {\n        Log::logger->log(Log::DEBUG, \"Aq backend has no session, omitting full DRM node checks\");\n\n        const auto DRMV = drmGetVersion(g_pCompositor->m_drm.fd);\n\n        if (DRMV) {\n            std::string name = std::string{DRMV->name, DRMV->name_len};\n            std::ranges::transform(name, name.begin(), tolower);\n\n            if (name.contains(\"nvidia\"))\n                m_nvidia = true;\n            else if (name.contains(\"i915\"))\n                m_intel = true;\n            else if (name.contains(\"softpipe\") || name.contains(\"Software Rasterizer\") || name.contains(\"llvmpipe\"))\n                m_software = true;\n\n            Log::logger->log(Log::DEBUG, \"Primary DRM driver information: {} v{}.{}.{} from {} description {}\", name, DRMV->version_major, DRMV->version_minor,\n                             DRMV->version_patchlevel, std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len});\n        } else {\n            Log::logger->log(Log::DEBUG, \"No primary DRM driver information found\");\n        }\n\n        drmFreeVersion(DRMV);\n    }\n\n    if (m_nvidia)\n        Log::logger->log(Log::WARN, \"NVIDIA detected, please remember to follow nvidia instructions on the wiki\");\n\n    // cursor hiding stuff\n\n    static auto P = Event::bus()->m_events.input.keyboard.key.listen([&](IKeyboard::SKeyEvent e, Event::SCallbackInfo&) {\n        if (m_cursorHiddenConditions.hiddenOnKeyboard)\n            return;\n\n        m_cursorHiddenConditions.hiddenOnKeyboard = true;\n        ensureCursorRenderingMode();\n    });\n\n    static auto P2 = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D pos, Event::SCallbackInfo&) {\n        if (!m_cursorHiddenConditions.hiddenOnKeyboard && m_cursorHiddenConditions.hiddenOnTouch == g_pInputManager->m_lastInputTouch &&\n            m_cursorHiddenConditions.hiddenOnTablet == g_pInputManager->m_lastInputTablet && !m_cursorHiddenConditions.hiddenOnTimeout)\n            return;\n\n        m_cursorHiddenConditions.hiddenOnKeyboard = false;\n        m_cursorHiddenConditions.hiddenOnTimeout  = false;\n        m_cursorHiddenConditions.hiddenOnTouch    = g_pInputManager->m_lastInputTouch;\n        m_cursorHiddenConditions.hiddenOnTablet   = g_pInputManager->m_lastInputTablet;\n        ensureCursorRenderingMode();\n    });\n\n    static auto P3 = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) {\n        g_pEventLoopManager->doLater([this]() {\n            if (!g_pHyprError->active())\n                return;\n            for (auto& m : g_pCompositor->m_monitors) {\n                arrangeLayersForMonitor(m->m_id);\n            }\n        });\n    });\n\n    static auto P4 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW window) {\n        if (window->m_ruleApplicator->renderUnfocused().valueOrDefault())\n            addWindowToRenderUnfocused(window);\n    });\n\n    m_cursorTicker = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, cursorTicker, nullptr);\n    wl_event_source_timer_update(m_cursorTicker, 500);\n\n    m_renderUnfocusedTimer = makeShared<CEventLoopTimer>(\n        std::nullopt,\n        [this](SP<CEventLoopTimer> self, void* data) {\n            static auto PFPS = CConfigValue<Hyprlang::INT>(\"misc:render_unfocused_fps\");\n\n            if (m_renderUnfocused.empty())\n                return;\n\n            bool dirty = false;\n            for (auto& w : m_renderUnfocused) {\n                if (!w) {\n                    dirty = true;\n                    continue;\n                }\n\n                if (!w->wlSurface() || !w->wlSurface()->resource() || shouldRenderWindow(w.lock()))\n                    continue;\n\n                w->wlSurface()->resource()->frame(Time::steadyNow());\n                auto FEEDBACK = makeUnique<CQueuedPresentationData>(w->wlSurface()->resource());\n                FEEDBACK->attachMonitor(Desktop::focusState()->monitor());\n                FEEDBACK->discarded();\n                PROTO::presentation->queueData(std::move(FEEDBACK));\n            }\n\n            if (dirty)\n                std::erase_if(m_renderUnfocused, [](const auto& e) { return !e || !e->m_ruleApplicator->renderUnfocused().valueOr(false); });\n\n            if (!m_renderUnfocused.empty())\n                m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS));\n        },\n        nullptr);\n\n    g_pEventLoopManager->addTimer(m_renderUnfocusedTimer);\n}\n\nIHyprRenderer::~IHyprRenderer() {\n    if (m_cursorTicker)\n        wl_event_source_remove(m_cursorTicker);\n}\n\nWP<CHyprOpenGLImpl> IHyprRenderer::glBackend() {\n    return g_pHyprOpenGL;\n}\n\nbool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) {\n    if (!pWindow->visibleOnMonitor(pMonitor))\n        return false;\n\n    if (!pWindow->m_workspace && !pWindow->m_fadingOut)\n        return false;\n\n    if (!pWindow->m_workspace && pWindow->m_fadingOut)\n        return pWindow->workspaceID() == pMonitor->activeWorkspaceID() || pWindow->workspaceID() == pMonitor->activeSpecialWorkspaceID();\n\n    if (pWindow->m_pinned)\n        return true;\n\n    // if the window is being moved to a workspace that is not invisible, and the alpha is > 0.F, render it.\n    if (pWindow->m_monitorMovedFrom != -1 && pWindow->m_movingToWorkspaceAlpha->isBeingAnimated() && pWindow->m_movingToWorkspaceAlpha->value() > 0.F && pWindow->m_workspace &&\n        !pWindow->m_workspace->isVisible())\n        return true;\n\n    const auto PWINDOWWORKSPACE = pWindow->m_workspace;\n    if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_monitor == pMonitor) {\n        if (PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() || PWINDOWWORKSPACE->m_alpha->isBeingAnimated() || PWINDOWWORKSPACE->m_forceRendering)\n            return true;\n\n        // if hidden behind fullscreen\n        if (PWINDOWWORKSPACE->m_hasFullscreenWindow && !pWindow->isFullscreen() && (!pWindow->m_isFloating || !pWindow->m_createdOverFullscreen) && pWindow->m_alpha->value() == 0)\n            return false;\n\n        if (!PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !PWINDOWWORKSPACE->m_alpha->isBeingAnimated() && !PWINDOWWORKSPACE->isVisible())\n            return false;\n    }\n\n    if (pWindow->m_monitor == pMonitor)\n        return true;\n\n    if ((!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) && pWindow->m_monitor != pMonitor)\n        return false;\n\n    // if not, check if it maybe is active on a different monitor.\n    if (pWindow->m_workspace && pWindow->m_workspace->isVisible() && pWindow->m_isFloating /* tiled windows can't be multi-ws */)\n        return !pWindow->isFullscreen(); // Do not draw fullscreen windows on other monitors\n\n    if (pMonitor->m_activeSpecialWorkspace == pWindow->m_workspace)\n        return true;\n\n    // if window is tiled and it's flying in, don't render on other mons (for slide)\n    if (!pWindow->m_isFloating && pWindow->m_realPosition->isBeingAnimated() && pWindow->m_animatingIn && pWindow->m_monitor != pMonitor)\n        return false;\n\n    if (pWindow->m_realPosition->isBeingAnimated()) {\n        if (PWINDOWWORKSPACE && !PWINDOWWORKSPACE->m_isSpecialWorkspace && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated())\n            return false;\n        // render window if window and monitor intersect\n        // (when moving out of or through a monitor)\n        CBox windowBox = pWindow->getFullWindowBoundingBox();\n        if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated())\n            windowBox.translate(PWINDOWWORKSPACE->m_renderOffset->value());\n        windowBox.translate(pWindow->m_floatingOffset);\n\n        const CBox monitorBox = {pMonitor->m_position, pMonitor->m_size};\n        if (!windowBox.intersection(monitorBox).empty() && (pWindow->workspaceID() == pMonitor->activeWorkspaceID() || pWindow->m_monitorMovedFrom != -1))\n            return true;\n    }\n\n    return false;\n}\n\nbool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) {\n\n    if (!validMapped(pWindow))\n        return false;\n\n    const auto PWORKSPACE = pWindow->m_workspace;\n\n    if (!pWindow->m_workspace)\n        return false;\n\n    if (pWindow->m_pinned || PWORKSPACE->m_forceRendering)\n        return true;\n\n    if (PWORKSPACE && PWORKSPACE->isVisible())\n        return true;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (PWORKSPACE && PWORKSPACE->m_monitor == m && (PWORKSPACE->m_renderOffset->isBeingAnimated() || PWORKSPACE->m_alpha->isBeingAnimated()))\n            return true;\n\n        if (m->m_activeSpecialWorkspace && pWindow->onSpecialWorkspace())\n            return true;\n    }\n\n    return false;\n}\n\nvoid IHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) {\n    PHLWINDOW pWorkspaceWindow = nullptr;\n\n    Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS);\n\n    // loop over the tiled windows that are fading out\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!shouldRenderWindow(w, pMonitor))\n            continue;\n\n        if (w->m_alpha->value() == 0.f)\n            continue;\n\n        if (w->isFullscreen() || w->m_isFloating)\n            continue;\n\n        if (pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);\n    }\n\n    // and floating ones too\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (!shouldRenderWindow(w, pMonitor))\n            continue;\n\n        if (w->m_alpha->value() == 0.f)\n            continue;\n\n        if (w->isFullscreen() || !w->m_isFloating)\n            continue;\n\n        if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)\n            continue; // special on another are rendered as a part of the base pass\n\n        renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);\n    }\n\n    // TODO: this pass sucks\n    for (auto const& w : g_pCompositor->m_windows) {\n        const auto PWORKSPACE = w->m_workspace;\n\n        if (w->m_workspace != pWorkspace || !w->isFullscreen()) {\n            if (!(PWORKSPACE && (PWORKSPACE->m_renderOffset->isBeingAnimated() || PWORKSPACE->m_alpha->isBeingAnimated() || PWORKSPACE->m_forceRendering)))\n                continue;\n\n            if (w->m_monitor != pMonitor)\n                continue;\n        }\n\n        if (!w->isFullscreen())\n            continue;\n\n        if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        if (shouldRenderWindow(w, pMonitor))\n            renderWindow(w, pMonitor, time, pWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN, RENDER_PASS_ALL);\n\n        if (w->m_workspace != pWorkspace)\n            continue;\n\n        pWorkspaceWindow = w;\n    }\n\n    if (!pWorkspaceWindow) {\n        // ?? happens sometimes...\n        pWorkspace->m_hasFullscreenWindow = false;\n        return; // this will produce one blank frame. Oh well.\n    }\n\n    // then render windows over fullscreen.\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->workspaceID() != pWorkspaceWindow->workspaceID() || !w->m_isFloating || (!w->m_createdOverFullscreen && !w->m_pinned) || (!w->m_isMapped && !w->m_fadingOut) ||\n            w->isFullscreen())\n            continue;\n\n        if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)\n            continue; // special on another are rendered as a part of the base pass\n\n        renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);\n    }\n}\n\nvoid IHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) {\n    PHLWINDOW lastWindow;\n\n    Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS);\n\n    std::vector<PHLWINDOWREF> windows, tiledFadingOut;\n    windows.reserve(g_pCompositor->m_windows.size());\n\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->isHidden() || (!w->m_isMapped && !w->m_fadingOut))\n            continue;\n\n        if (!shouldRenderWindow(w, pMonitor))\n            continue;\n\n        windows.emplace_back(w);\n    }\n\n    // Non-floating main\n    for (auto& w : windows) {\n        if (w->m_isFloating)\n            continue; // floating are in the second pass\n\n        // some things may force us to ignore the special/not special disparity\n        const bool IGNORE_SPECIAL_CHECK = w->m_monitorMovedFrom != -1 && (w->m_workspace && !w->m_workspace->isVisible());\n\n        if (!IGNORE_SPECIAL_CHECK && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        // render active window after all others of this pass\n        if (w == Desktop::focusState()->window()) {\n            lastWindow = w.lock();\n            continue;\n        }\n\n        // render tiled fading out after others\n        if (w->m_fadingOut) {\n            tiledFadingOut.emplace_back(w);\n            w.reset();\n            continue;\n        }\n\n        // render the bad boy\n        renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_MAIN);\n        w.reset();\n    }\n\n    if (lastWindow)\n        renderWindow(lastWindow, pMonitor, time, true, RENDER_PASS_MAIN);\n\n    lastWindow.reset();\n\n    // render tiled windows that are fading out after other tiled to not hide them behind\n    for (auto& w : tiledFadingOut) {\n        renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_MAIN);\n    }\n\n    // Non-floating popup\n    for (auto& w : windows) {\n        if (!w)\n            continue;\n\n        if (w->m_isFloating)\n            continue; // floating are in the second pass\n\n        // some things may force us to ignore the special/not special disparity\n        const bool IGNORE_SPECIAL_CHECK = w->m_monitorMovedFrom != -1 && (w->m_workspace && !w->m_workspace->isVisible());\n\n        if (!IGNORE_SPECIAL_CHECK && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        // render the bad boy\n        renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_POPUP);\n        w.reset();\n    }\n\n    // floating on top\n    for (auto& w : windows) {\n        if (!w)\n            continue;\n\n        if (!w->m_isFloating || w->m_pinned)\n            continue;\n\n        // some things may force us to ignore the special/not special disparity\n        const bool IGNORE_SPECIAL_CHECK = w->m_monitorMovedFrom != -1 && (w->m_workspace && !w->m_workspace->isVisible());\n\n        if (!IGNORE_SPECIAL_CHECK && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())\n            continue;\n\n        if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)\n            continue; // special on another are rendered as a part of the base pass\n\n        // render the bad boy\n        renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_ALL);\n    }\n}\n\nvoid IHyprRenderer::bindOffMain() {\n    RASSERT(m_renderData.pMonitor->m_offMainFB->isAllocated(), \"IHyprRenderer::beginRender should allocate monitor FBs\")\n\n    m_renderData.pMonitor->m_offMainFB->bind();\n    draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), {});\n    m_renderData.currentFB = m_renderData.pMonitor->m_offMainFB;\n}\n\nvoid IHyprRenderer::bindBackOnMain() {\n    m_renderData.mainFB->bind();\n    m_renderData.currentFB = m_renderData.mainFB;\n}\n\nvoid IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const Time::steady_tp& time, bool decorate, eRenderPassMode mode, bool ignorePosition, bool standalone) {\n    if (pWindow->isHidden() && !standalone)\n        return;\n\n    if (pWindow->m_fadingOut) {\n        if (pMonitor == pWindow->m_monitor) // TODO: fix this\n            renderSnapshot(pWindow);\n        return;\n    }\n\n    if (!pWindow->m_isMapped)\n        return;\n\n    TRACY_GPU_ZONE(\"RenderWindow\");\n\n    const auto                       PWORKSPACE = pWindow->m_workspace;\n    const auto                       REALPOS    = pWindow->m_realPosition->value() + (pWindow->m_pinned ? Vector2D{} : PWORKSPACE->m_renderOffset->value());\n    static auto                      PDIMAROUND = CConfigValue<Hyprlang::FLOAT>(\"decoration:dim_around\");\n\n    CSurfacePassElement::SRenderData renderdata = {pMonitor, time};\n    CBox                             textureBox = {REALPOS.x, REALPOS.y, std::max(pWindow->m_realSize->value().x, 5.0), std::max(pWindow->m_realSize->value().y, 5.0)};\n\n    renderdata.pos.x = textureBox.x;\n    renderdata.pos.y = textureBox.y;\n    renderdata.w     = textureBox.w;\n    renderdata.h     = textureBox.h;\n\n    if (ignorePosition) {\n        renderdata.pos.x = pMonitor->m_position.x;\n        renderdata.pos.y = pMonitor->m_position.y;\n    } else {\n        const bool ANR = pWindow->isNotResponding();\n        if (ANR && pWindow->m_notRespondingTint->goal() != 0.2F)\n            *pWindow->m_notRespondingTint = 0.2F;\n        else if (!ANR && pWindow->m_notRespondingTint->goal() != 0.F)\n            *pWindow->m_notRespondingTint = 0.F;\n    }\n\n    if (standalone)\n        decorate = false;\n\n    // whether to use m_fMovingToWorkspaceAlpha, only if fading out into an invisible ws\n    const bool USE_WORKSPACE_FADE_ALPHA = pWindow->m_monitorMovedFrom != -1 && (!PWORKSPACE || !PWORKSPACE->isVisible());\n\n    renderdata.surface   = pWindow->wlSurface()->resource();\n    renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);\n    renderdata.fadeAlpha = pWindow->m_alpha->value() * (pWindow->m_pinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_alpha->value()) *\n        (USE_WORKSPACE_FADE_ALPHA ? pWindow->m_movingToWorkspaceAlpha->value() : 1.F) * pWindow->m_movingFromWorkspaceAlpha->value();\n    renderdata.alpha         = pWindow->m_activeInactiveAlpha->value();\n    renderdata.decorate      = decorate && !pWindow->m_X11DoesntWantBorders && !pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);\n    renderdata.rounding      = standalone || renderdata.dontRound ? 0 : pWindow->rounding() * pMonitor->m_scale;\n    renderdata.roundingPower = standalone || renderdata.dontRound ? 2.0f : pWindow->roundingPower();\n    renderdata.blur          = !standalone && shouldBlur(pWindow);\n    renderdata.pWindow       = pWindow;\n\n    if (standalone) {\n        renderdata.alpha     = 1.f;\n        renderdata.fadeAlpha = 1.f;\n    }\n\n    // apply opaque\n    if (pWindow->m_ruleApplicator->opaque().valueOrDefault())\n        renderdata.alpha = 1.f;\n\n    renderdata.pWindow = pWindow;\n\n    // for plugins\n    m_renderData.currentWindow = pWindow;\n\n    Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOW);\n\n    const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha;\n\n    if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) {\n        CBox                        monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};\n        CRectPassElement::SRectData data;\n        data.color = CHyprColor(0, 0, 0, *PDIMAROUND * fullAlpha);\n        data.box   = monbox;\n        m_renderPass.add(makeUnique<CRectPassElement>(data));\n    }\n\n    renderdata.pos.x += pWindow->m_floatingOffset.x;\n    renderdata.pos.y += pWindow->m_floatingOffset.y;\n\n    // if window is floating and we have a slide animation, clip it to its full bb\n    if (!ignorePosition && pWindow->m_isFloating && !pWindow->isFullscreen() && PWORKSPACE->m_renderOffset->isBeingAnimated() && !pWindow->m_pinned) {\n        CRegion rg =\n            pWindow->getFullWindowBoundingBox().translate(-pMonitor->m_position + PWORKSPACE->m_renderOffset->value() + pWindow->m_floatingOffset).scale(pMonitor->m_scale);\n        renderdata.clipBox = rg.getExtents();\n    }\n\n    // render window decorations first, if not fullscreen full\n    if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_MAIN) {\n\n        const bool TRANSFORMERSPRESENT = !pWindow->m_transformers.empty();\n\n        if (TRANSFORMERSPRESENT) {\n            bindOffMain();\n\n            for (auto const& t : pWindow->m_transformers) {\n                t->preWindowRender(&renderdata);\n            }\n        }\n\n        if (renderdata.decorate) {\n            for (auto const& wd : pWindow->m_windowDecorations) {\n                if (wd->getDecorationLayer() != DECORATION_LAYER_BOTTOM)\n                    continue;\n\n                wd->draw(pMonitor, fullAlpha);\n            }\n\n            for (auto const& wd : pWindow->m_windowDecorations) {\n                if (wd->getDecorationLayer() != DECORATION_LAYER_UNDER)\n                    continue;\n\n                wd->draw(pMonitor, fullAlpha);\n            }\n        }\n\n        static auto PXWLUSENN = CConfigValue<Hyprlang::INT>(\"xwayland:use_nearest_neighbor\");\n        if ((pWindow->m_isX11 && *PXWLUSENN) || pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault())\n            renderdata.useNearestNeighbor = true;\n\n        if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall && renderdata.blur) {\n            CBox wb = {renderdata.pos.x - pMonitor->m_position.x, renderdata.pos.y - pMonitor->m_position.y, renderdata.w, renderdata.h};\n            wb.scale(pMonitor->m_scale).round();\n            CRectPassElement::SRectData data;\n            data.color = CHyprColor(0, 0, 0, 0);\n            data.box   = wb;\n            data.round = renderdata.dontRound ? 0 : renderdata.rounding - 1;\n            data.blur  = true;\n            data.blurA = renderdata.fadeAlpha;\n            data.xray  = shouldUseNewBlurOptimizations(nullptr, pWindow);\n            m_renderPass.add(makeUnique<CRectPassElement>(data));\n            renderdata.blur = false;\n        }\n\n        renderdata.surfaceCounter = 0;\n        pWindow->wlSurface()->resource()->breadthfirst(\n            [this, &renderdata, &pWindow](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {\n                if (!s->m_current.texture)\n                    return;\n\n                if (s->m_current.size.x < 1 || s->m_current.size.y < 1)\n                    return;\n\n                renderdata.localPos    = offset;\n                renderdata.texture     = s->m_current.texture;\n                renderdata.surface     = s;\n                renderdata.mainSurface = s == pWindow->wlSurface()->resource();\n                m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n                renderdata.surfaceCounter++;\n            },\n            nullptr);\n\n        renderdata.useNearestNeighbor = false;\n\n        if (renderdata.decorate) {\n            for (auto const& wd : pWindow->m_windowDecorations) {\n                if (wd->getDecorationLayer() != DECORATION_LAYER_OVER)\n                    continue;\n\n                wd->draw(pMonitor, fullAlpha);\n            }\n        }\n\n        if (TRANSFORMERSPRESENT) {\n            IFramebuffer* last = m_renderData.currentFB.get();\n            for (auto const& t : pWindow->m_transformers) {\n                last = t->transform(last);\n            }\n\n            bindBackOnMain();\n            renderOffToMain(last);\n        }\n    }\n\n    m_renderData.clipBox = CBox();\n\n    if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_POPUP) {\n        if (!pWindow->m_isX11) {\n            CBox geom = pWindow->m_xdgSurface->m_current.geometry;\n\n            renderdata.pos -= geom.pos();\n            renderdata.dontRound       = true; // don't round popups\n            renderdata.pMonitor        = pMonitor;\n            renderdata.squishOversized = false; // don't squish popups\n            renderdata.popup           = true;\n\n            static CConfigValue PBLURIGNOREA = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:popups_ignorealpha\");\n\n            renderdata.blur = shouldBlur(pWindow->m_popupHead);\n\n            if (renderdata.blur) {\n                renderdata.discardMode |= DISCARD_ALPHA;\n                renderdata.discardOpacity = *PBLURIGNOREA;\n            }\n\n            if (pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault())\n                renderdata.useNearestNeighbor = true;\n\n            renderdata.surfaceCounter = 0;\n\n            pWindow->m_popupHead->breadthfirst(\n                [this, &renderdata](WP<Desktop::View::CPopup> popup, void* data) {\n                    if (popup->m_fadingOut) {\n                        renderSnapshot(popup);\n                        return;\n                    }\n\n                    if (!popup->aliveAndVisible())\n                        return;\n\n                    const auto     pos    = popup->coordsRelativeToParent();\n                    const Vector2D oldPos = renderdata.pos;\n                    renderdata.pos += pos;\n                    renderdata.fadeAlpha = popup->m_alpha->value();\n\n                    popup->wlSurface()->resource()->breadthfirst(\n                        [this, &renderdata](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {\n                            if (!s->m_current.texture)\n                                return;\n\n                            if (s->m_current.size.x < 1 || s->m_current.size.y < 1)\n                                return;\n\n                            renderdata.localPos    = offset;\n                            renderdata.texture     = s->m_current.texture;\n                            renderdata.surface     = s;\n                            renderdata.mainSurface = false;\n                            m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n                            renderdata.surfaceCounter++;\n                        },\n                        data);\n\n                    renderdata.pos = oldPos;\n                },\n                &renderdata);\n\n            renderdata.alpha = 1.F;\n        }\n\n        if (decorate) {\n            for (auto const& wd : pWindow->m_windowDecorations) {\n                if (wd->getDecorationLayer() != DECORATION_LAYER_OVERLAY)\n                    continue;\n\n                wd->draw(pMonitor, fullAlpha);\n            }\n        }\n    }\n\n    Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOW);\n\n    m_renderData.currentWindow.reset();\n}\n\nvoid IHyprRenderer::drawRect(CRectPassElement* element, const CRegion& damage) {\n    auto& data = element->m_data;\n\n    if (data.box.w <= 0 || data.box.h <= 0)\n        return;\n\n    if (!data.clipBox.empty())\n        m_renderData.clipBox = data.clipBox;\n\n    data.modifiedBox = data.box;\n    m_renderData.renderModif.applyToBox(data.modifiedBox);\n\n    CBox transformedBox = data.box;\n    transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,\n                             m_renderData.pMonitor->m_transformedSize.y);\n\n    data.TOPLEFT[0]  = sc<float>(transformedBox.x);\n    data.TOPLEFT[1]  = sc<float>(transformedBox.y);\n    data.FULLSIZE[0] = sc<float>(transformedBox.width);\n    data.FULLSIZE[1] = sc<float>(transformedBox.height);\n\n    data.drawRegion = data.color.a == 1.F || !data.blur ? damage : m_renderData.damage;\n\n    if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) {\n        CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height};\n        data.drawRegion = damageClip.intersect(data.drawRegion);\n    }\n\n    draw(element, damage);\n\n    m_renderData.clipBox = {};\n}\n\nvoid IHyprRenderer::drawHints(CRendererHintsPassElement* element, const CRegion& damage) {\n    const auto m_data = element->m_data;\n    if (m_data.renderModif.has_value())\n        m_renderData.renderModif = *m_data.renderModif;\n}\n\nvoid IHyprRenderer::drawPreBlur(CPreBlurElement* element, const CRegion& damage) {\n    TRACY_GPU_ZONE(\"RenderPreBlurForCurrentMonitor\");\n\n    const auto SAVEDRENDERMODIF = m_renderData.renderModif;\n    m_renderData.renderModif    = {}; // fix shit\n\n    // make the fake dmg\n    CRegion fakeDamage{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};\n\n    draw(element, fakeDamage);\n\n    m_renderData.pMonitor->m_blurFBDirty        = false;\n    m_renderData.pMonitor->m_blurFBShouldRender = false;\n\n    m_renderData.renderModif = SAVEDRENDERMODIF;\n}\n\nvoid IHyprRenderer::drawSurface(CSurfacePassElement* element, const CRegion& damage) {\n    const auto  m_data = element->m_data;\n\n    CScopeGuard x = {[]() {\n        g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n        g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);\n    }};\n\n    if (!m_data.texture)\n        return;\n\n    const auto& TEXTURE = m_data.texture;\n\n    // this is bad, probably has been logged elsewhere. Means the texture failed\n    // uploading to the GPU.\n    if (!TEXTURE->ok())\n        return;\n\n    const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE;\n    TRACY_GPU_ZONE(\"RenderSurface\");\n\n    auto        PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);\n\n    const float ALPHA         = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier : 1.F);\n    const float OVERALL_ALPHA = PSURFACE ? PSURFACE->m_overallOpacity : 1.F;\n    const bool  BLUR          = m_data.blur && (!TEXTURE->m_opaque || ALPHA < 1.F || OVERALL_ALPHA < 1.F);\n\n    auto        windowBox = element->getTexBox();\n\n    const auto  PROJSIZEUNSCALED = windowBox.size();\n\n    windowBox.scale(m_data.pMonitor->m_scale);\n    windowBox.round();\n\n    if (windowBox.width <= 1 || windowBox.height <= 1) {\n        element->discard();\n        return;\n    }\n\n    const bool MISALIGNEDFSV1 = std::floor(m_data.pMonitor->m_scale) != m_data.pMonitor->m_scale /* Fractional */ && m_data.surface->m_current.scale == 1 /* fs protocol */ &&\n        windowBox.size() != m_data.surface->m_current.bufferSize /* misaligned */ && DELTALESSTHAN(windowBox.width, m_data.surface->m_current.bufferSize.x, 3) &&\n        DELTALESSTHAN(windowBox.height, m_data.surface->m_current.bufferSize.y, 3) /* off by one-or-two */ &&\n        (!m_data.pWindow || (!m_data.pWindow->m_realSize->isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */ &&\n        (!m_data.pLS || (!m_data.pLS->m_realSize->isBeingAnimated())); /* not LS or not animated */\n\n    calculateUVForSurface(m_data.pWindow, m_data.surface, m_data.pMonitor->m_self.lock(), m_data.mainSurface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1);\n\n    auto cancelRender = false;\n    auto clipRegion   = element->visibleRegion(cancelRender);\n    if (cancelRender)\n        return;\n\n    // check for fractional scale surfaces misaligning the buffer size\n    // in those cases it's better to just force nearest neighbor\n    // as long as the window is not animated. During those it'd look weird.\n    // UV will fixup it as well\n    if (MISALIGNEDFSV1)\n        m_renderData.useNearestNeighbor = true;\n\n    float rounding      = m_data.rounding;\n    float roundingPower = m_data.roundingPower;\n\n    rounding -= 1; // to fix a border issue\n\n    if (m_data.dontRound) {\n        rounding      = 0;\n        roundingPower = 2.0f;\n    }\n\n    const bool WINDOWOPAQUE    = m_data.pWindow && m_data.pWindow->wlSurface()->resource() == m_data.surface ? m_data.pWindow->opaque() : false;\n    const bool CANDISABLEBLEND = ALPHA >= 1.f && OVERALL_ALPHA >= 1.f && rounding <= 0 && WINDOWOPAQUE;\n\n    if (CANDISABLEBLEND)\n        blend(false);\n    else\n        blend(true);\n\n    // FIXME: This is wrong and will bug the blur out as shit if the first surface\n    // is a subsurface that does NOT cover the entire frame. In such cases, we probably should fall back\n    // to what we do for misaligned surfaces (blur the entire thing and then render shit without blur)\n    if (m_data.surfaceCounter == 0 && !m_data.popup) {\n        if (BLUR)\n            draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                     .tex                   = TEXTURE,\n                     .box                   = windowBox,\n                     .a                     = ALPHA,\n                     .blurA                 = m_data.fadeAlpha,\n                     .overallA              = OVERALL_ALPHA,\n                     .round                 = rounding,\n                     .roundingPower         = roundingPower,\n                     .blur                  = true,\n                     .blockBlurOptimization = m_data.blockBlurOptimization,\n                     .allowCustomUV         = true,\n                     .surface               = m_data.surface,\n                     .discardMode           = m_data.discardMode,\n                     .discardOpacity        = m_data.discardOpacity,\n                     .clipRegion            = clipRegion,\n                     .currentLS             = m_data.pLS,\n                 }),\n                 m_renderData.damage.copy().intersect(windowBox));\n        else\n            draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                     .tex            = TEXTURE,\n                     .box            = windowBox,\n                     .a              = ALPHA * OVERALL_ALPHA,\n                     .round          = rounding,\n                     .roundingPower  = roundingPower,\n                     .discardActive  = false,\n                     .allowCustomUV  = true,\n                     .surface        = m_data.surface,\n                     .discardMode    = m_data.discardMode,\n                     .discardOpacity = m_data.discardOpacity,\n                     .clipRegion     = clipRegion,\n                     .currentLS      = m_data.pLS,\n                 }),\n                 m_renderData.damage.copy().intersect(windowBox));\n    } else {\n        if (BLUR && m_data.popup)\n            draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                     .tex                   = TEXTURE,\n                     .box                   = windowBox,\n                     .a                     = ALPHA,\n                     .blurA                 = m_data.fadeAlpha,\n                     .overallA              = OVERALL_ALPHA,\n                     .round                 = rounding,\n                     .roundingPower         = roundingPower,\n                     .blur                  = true,\n                     .blockBlurOptimization = true,\n                     .allowCustomUV         = true,\n                     .surface               = m_data.surface,\n                     .discardMode           = m_data.discardMode,\n                     .discardOpacity        = m_data.discardOpacity,\n                     .clipRegion            = clipRegion,\n                     .currentLS             = m_data.pLS,\n                 }),\n                 m_renderData.damage.copy().intersect(windowBox));\n        else\n            draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n                     .tex            = TEXTURE,\n                     .box            = windowBox,\n                     .a              = ALPHA * OVERALL_ALPHA,\n                     .round          = rounding,\n                     .roundingPower  = roundingPower,\n                     .discardActive  = false,\n                     .allowCustomUV  = true,\n                     .surface        = m_data.surface,\n                     .discardMode    = m_data.discardMode,\n                     .discardOpacity = m_data.discardOpacity,\n                     .clipRegion     = clipRegion,\n                     .currentLS      = m_data.pLS,\n                 }),\n                 m_renderData.damage.copy().intersect(windowBox));\n    }\n\n    blend(true);\n};\n\nvoid IHyprRenderer::preDrawSurface(CSurfacePassElement* element, const CRegion& damage) {\n    m_renderData.clipBox            = element->m_data.clipBox;\n    m_renderData.useNearestNeighbor = element->m_data.useNearestNeighbor;\n    pushMonitorTransformEnabled(element->m_data.flipEndFrame);\n    m_renderData.currentWindow = element->m_data.pWindow;\n\n    drawSurface(element, damage);\n\n    if (!m_bBlockSurfaceFeedback)\n        element->m_data.surface->presentFeedback(element->m_data.when, element->m_data.pMonitor->m_self.lock());\n\n    // add async (dmabuf) buffers to usedBuffers so we can handle release later\n    // sync (shm) buffers will be released in commitState, so no need to track them here\n    if (element->m_data.surface->m_current.buffer && !element->m_data.surface->m_current.buffer->isSynchronous())\n        m_usedAsyncBuffers.emplace_back(element->m_data.surface->m_current.buffer);\n\n    m_renderData.clipBox            = {};\n    m_renderData.useNearestNeighbor = false;\n    popMonitorTransformEnabled();\n    m_renderData.currentWindow.reset();\n}\n\nvoid IHyprRenderer::drawTex(CTexPassElement* element, const CRegion& damage) {\n    if (!element->m_data.clipBox.empty())\n        m_renderData.clipBox = element->m_data.clipBox;\n\n    pushMonitorTransformEnabled(element->m_data.flipEndFrame);\n    if (element->m_data.useMirrorProjection)\n        setProjectionType(RPT_MIRROR);\n\n    m_renderData.surface = element->m_data.surface;\n\n    CScopeGuard x = {[useMirrorProjection = element->m_data.useMirrorProjection]() {\n        g_pHyprRenderer->popMonitorTransformEnabled();\n        if (useMirrorProjection)\n            g_pHyprRenderer->setProjectionType(RPT_MONITOR);\n        g_pHyprRenderer->m_renderData.surface.reset();\n    }};\n\n    if (element->m_data.blur) {\n        // make a damage region for this window\n        CRegion texDamage{m_renderData.damage};\n        texDamage.intersect(element->m_data.box.x, element->m_data.box.y, element->m_data.box.width, element->m_data.box.height);\n\n        // While renderTextureInternalWithDamage will clip the blur as well,\n        // clipping texDamage here allows blur generation to be optimized.\n        if (!element->m_data.clipRegion.empty())\n            texDamage.intersect(element->m_data.clipRegion);\n\n        if (texDamage.empty())\n            return;\n\n        m_renderData.renderModif.applyToRegion(texDamage);\n\n        element->m_data.damage = texDamage;\n\n        // amazing hack: the surface has an opaque region!\n        const auto& surface = element->m_data.surface;\n        const auto& box     = element->m_data.box;\n        CRegion     inverseOpaque;\n        if (element->m_data.a >= 1.f && surface && std::round(surface->m_current.size.x * m_renderData.pMonitor->m_scale) == box.w &&\n            std::round(surface->m_current.size.y * m_renderData.pMonitor->m_scale) == box.h) {\n            pixman_box32_t surfbox = {0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale};\n            inverseOpaque          = surface->m_current.opaque;\n            inverseOpaque.invert(&surfbox).intersect(0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale);\n\n            if (inverseOpaque.empty()) {\n                element->m_data.blur = false;\n                draw(element, damage);\n                m_renderData.clipBox = {};\n                return;\n            }\n        } else\n            inverseOpaque = {0, 0, element->m_data.box.width, element->m_data.box.height};\n\n        inverseOpaque.scale(m_renderData.pMonitor->m_scale);\n        element->m_data.blockBlurOptimization =\n            element->m_data.blockBlurOptimization.value_or(false) || !shouldUseNewBlurOptimizations(element->m_data.currentLS.lock(), m_renderData.currentWindow.lock());\n\n        //   vvv TODO: layered blur fbs?\n        if (element->m_data.blockBlurOptimization.value_or(false)) {\n            inverseOpaque.translate(box.pos());\n            m_renderData.renderModif.applyToRegion(inverseOpaque);\n            inverseOpaque.intersect(element->m_data.damage);\n            element->m_data.blurredBG = blurMainFramebuffer(element->m_data.a, &inverseOpaque);\n            m_renderData.currentFB->bind();\n        } else\n            element->m_data.blurredBG = m_renderData.pMonitor->m_blurFB ? m_renderData.pMonitor->m_blurFB->getTexture() : nullptr;\n\n        draw(element, damage);\n    } else\n        draw(element, damage);\n\n    m_renderData.clipBox = {};\n}\n\nvoid IHyprRenderer::drawTexMatte(CTextureMatteElement* element, const CRegion& damage) {\n    if (m_renderData.damage.empty())\n        return;\n\n    const auto m_data = element->m_data;\n    if (m_data.disableTransformAndModify) {\n        pushMonitorTransformEnabled(true);\n        m_renderData.renderModif.enabled = false;\n        draw(element, damage);\n        m_renderData.renderModif.enabled = true;\n        popMonitorTransformEnabled();\n    } else\n        draw(element, damage);\n}\n\nvoid IHyprRenderer::draw(WP<IPassElement> element, const CRegion& damage) {\n    if (!element)\n        return;\n\n    switch (element->type()) {\n        case EK_BORDER: draw(dc<CBorderPassElement*>(element.get()), damage); break;\n        case EK_CLEAR: draw(dc<CClearPassElement*>(element.get()), damage); break;\n        case EK_FRAMEBUFFER: draw(dc<CFramebufferElement*>(element.get()), damage); break;\n        case EK_PRE_BLUR: drawPreBlur(dc<CPreBlurElement*>(element.get()), damage); break;\n        case EK_RECT: drawRect(dc<CRectPassElement*>(element.get()), damage); break;\n        case EK_HINTS: drawHints(dc<CRendererHintsPassElement*>(element.get()), damage); break;\n        case EK_SHADOW: draw(dc<CShadowPassElement*>(element.get()), damage); break;\n        case EK_SURFACE: preDrawSurface(dc<CSurfacePassElement*>(element.get()), damage); break;\n        case EK_TEXTURE: drawTex(dc<CTexPassElement*>(element.get()), damage); break;\n        case EK_TEXTURE_MATTE: drawTexMatte(dc<CTextureMatteElement*>(element.get()), damage); break;\n        default: Log::logger->log(Log::WARN, \"Unimplimented draw for {}\", element->passName());\n    }\n}\n\nbool IHyprRenderer::preBlurQueued(PHLMONITORREF pMonitor) {\n    static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>(\"decoration:blur:new_optimizations\");\n    static auto PBLUR            = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n\n    if (!pMonitor)\n        return false;\n    return m_renderData.pMonitor->m_blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && m_renderData.pMonitor->m_blurFBShouldRender;\n}\n\nvoid IHyprRenderer::pushMonitorTransformEnabled(bool enabled) {\n    m_monitorTransformStack.push(enabled);\n    m_monitorTransformEnabled = enabled;\n}\n\nvoid IHyprRenderer::popMonitorTransformEnabled() {\n    m_monitorTransformStack.pop();\n    m_monitorTransformEnabled = m_monitorTransformStack.top();\n}\n\nbool IHyprRenderer::monitorTransformEnabled() {\n    return m_monitorTransformEnabled;\n}\n\nSP<ITexture> IHyprRenderer::createTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy) {\n    if (!buffer)\n        return createTexture();\n\n    auto attrs = buffer->dmabuf();\n\n    if (!attrs.success) {\n        // attempt shm\n        auto shm = buffer->shm();\n\n        if (!shm.success) {\n            Log::logger->log(Log::ERR, \"Cannot create a texture: buffer has no dmabuf or shm\");\n            return createTexture(buffer->opaque);\n        }\n\n        auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0);\n\n        return createTexture(fmt, pixelData, bufLen, shm.size, keepDataCopy, buffer->opaque);\n    }\n\n    auto tex = createTexture(attrs, buffer->opaque);\n\n    if (!tex) {\n        Log::logger->log(Log::ERR, \"Cannot create a texture: failed to create an Image\");\n        return createTexture(buffer->opaque);\n    }\n\n    return tex;\n}\n\nvoid IHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::steady_tp& time, bool popups, bool lockscreen) {\n    if (!pLayer)\n        return;\n\n    // skip rendering based on abovelock rule and make sure to not render abovelock layers twice\n    if ((pLayer->m_ruleApplicator->aboveLock().valueOrDefault() && !lockscreen && g_pSessionLockManager->isSessionLocked()) ||\n        (lockscreen && !pLayer->m_ruleApplicator->aboveLock().valueOrDefault()))\n        return;\n\n    static auto PDIMAROUND = CConfigValue<Hyprlang::FLOAT>(\"decoration:dim_around\");\n\n    if (*PDIMAROUND && pLayer->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && !popups) {\n        CRectPassElement::SRectData data;\n        data.box   = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};\n        data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pLayer->m_alpha->value());\n        m_renderPass.add(makeUnique<CRectPassElement>(data));\n    }\n\n    if (pLayer->m_fadingOut) {\n        if (!popups)\n            renderSnapshot(pLayer);\n        return;\n    }\n\n    TRACY_GPU_ZONE(\"RenderLayer\");\n\n    const auto                       REALPOS = pLayer->m_realPosition->value();\n    const auto                       REALSIZ = pLayer->m_realSize->value();\n\n    CSurfacePassElement::SRenderData renderdata = {pMonitor, time, REALPOS};\n    renderdata.fadeAlpha                        = pLayer->m_alpha->value();\n    renderdata.blur                             = shouldBlur(pLayer);\n    renderdata.surface                          = pLayer->wlSurface()->resource();\n    renderdata.decorate                         = false;\n    renderdata.w                                = REALSIZ.x;\n    renderdata.h                                = REALSIZ.y;\n    renderdata.pLS                              = pLayer;\n    renderdata.blockBlurOptimization            = pLayer->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM || pLayer->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;\n\n    renderdata.clipBox = CBox{0, 0, pMonitor->m_size.x, pMonitor->m_size.y}.scale(pMonitor->m_scale);\n\n    if (renderdata.blur && pLayer->m_ruleApplicator->ignoreAlpha().hasValue()) {\n        renderdata.discardMode |= DISCARD_ALPHA;\n        renderdata.discardOpacity = pLayer->m_ruleApplicator->ignoreAlpha().valueOrDefault();\n    }\n\n    if (!popups)\n        pLayer->wlSurface()->resource()->breadthfirst(\n            [this, &renderdata, &pLayer](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {\n                if (!s->m_current.texture)\n                    return;\n\n                if (s->m_current.size.x < 1 || s->m_current.size.y < 1)\n                    return;\n\n                renderdata.localPos    = offset;\n                renderdata.texture     = s->m_current.texture;\n                renderdata.surface     = s;\n                renderdata.mainSurface = s == pLayer->wlSurface()->resource();\n                m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n                renderdata.surfaceCounter++;\n            },\n            &renderdata);\n\n    renderdata.squishOversized = false; // don't squish popups\n    renderdata.dontRound       = true;\n    renderdata.popup           = true;\n    renderdata.blur            = pLayer->m_ruleApplicator->blurPopups().valueOrDefault();\n    renderdata.surfaceCounter  = 0;\n    if (popups) {\n        pLayer->m_popupHead->breadthfirst(\n            [this, &renderdata](WP<Desktop::View::CPopup> popup, void* data) {\n                if (!popup->aliveAndVisible())\n                    return;\n\n                const auto SURF = popup->wlSurface()->resource();\n\n                if (!SURF->m_current.texture)\n                    return;\n\n                if (SURF->m_current.size.x < 1 || SURF->m_current.size.y < 1)\n                    return;\n\n                Vector2D pos           = popup->coordsRelativeToParent();\n                renderdata.localPos    = pos;\n                renderdata.texture     = SURF->m_current.texture;\n                renderdata.surface     = SURF;\n                renderdata.mainSurface = false;\n                m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n                renderdata.surfaceCounter++;\n            },\n            &renderdata);\n    }\n}\n\nvoid IHyprRenderer::renderIMEPopup(CInputPopup* pPopup, PHLMONITOR pMonitor, const Time::steady_tp& time) {\n    const auto                       POS = pPopup->globalBox().pos();\n\n    CSurfacePassElement::SRenderData renderdata = {pMonitor, time, POS};\n\n    const auto                       SURF = pPopup->getSurface();\n\n    renderdata.surface  = SURF;\n    renderdata.decorate = false;\n    renderdata.w        = SURF->m_current.size.x;\n    renderdata.h        = SURF->m_current.size.y;\n\n    static auto PBLUR        = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n    static auto PBLURIMES    = CConfigValue<Hyprlang::INT>(\"decoration:blur:input_methods\");\n    static auto PBLURIGNOREA = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:input_methods_ignorealpha\");\n\n    renderdata.blur = *PBLURIMES && *PBLUR;\n    if (renderdata.blur) {\n        renderdata.discardMode |= DISCARD_ALPHA;\n        renderdata.discardOpacity = *PBLURIGNOREA;\n    }\n\n    SURF->breadthfirst(\n        [this, &renderdata, &SURF](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {\n            if (!s->m_current.texture)\n                return;\n\n            if (s->m_current.size.x < 1 || s->m_current.size.y < 1)\n                return;\n\n            renderdata.localPos    = offset;\n            renderdata.texture     = s->m_current.texture;\n            renderdata.surface     = s;\n            renderdata.mainSurface = s == SURF;\n            m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n            renderdata.surfaceCounter++;\n        },\n        &renderdata);\n}\n\nvoid IHyprRenderer::renderSessionLockSurface(WP<SSessionLockSurface> pSurface, PHLMONITOR pMonitor, const Time::steady_tp& time) {\n    CSurfacePassElement::SRenderData renderdata = {pMonitor, time, pMonitor->m_position, pMonitor->m_position};\n\n    renderdata.blur     = false;\n    renderdata.surface  = pSurface->surface->surface();\n    renderdata.decorate = false;\n    renderdata.w        = pMonitor->m_size.x;\n    renderdata.h        = pMonitor->m_size.y;\n\n    renderdata.surface->breadthfirst(\n        [this, &renderdata, &pSurface](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {\n            if (!s->m_current.texture)\n                return;\n\n            if (s->m_current.size.x < 1 || s->m_current.size.y < 1)\n                return;\n\n            renderdata.localPos    = offset;\n            renderdata.texture     = s->m_current.texture;\n            renderdata.surface     = s;\n            renderdata.mainSurface = s == pSurface->surface->surface();\n            m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n            renderdata.surfaceCounter++;\n        },\n        &renderdata);\n}\n\nvoid IHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time, const Vector2D& translate, const float& scale) {\n    static auto PDIMSPECIAL      = CConfigValue<Hyprlang::FLOAT>(\"decoration:dim_special\");\n    static auto PBLURSPECIAL     = CConfigValue<Hyprlang::INT>(\"decoration:blur:special\");\n    static auto PBLUR            = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n    static auto PXPMODE          = CConfigValue<Hyprlang::INT>(\"render:xp_mode\");\n    static auto PSESSIONLOCKXRAY = CConfigValue<Hyprlang::INT>(\"misc:session_lock_xray\");\n\n    if UNLIKELY (!pMonitor)\n        return;\n\n    if UNLIKELY (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) {\n        // We stop to render workspaces as soon as the lockscreen was sent the \"locked\" or \"finished\" (aka denied) event.\n        // In addition we make sure to stop rendering workspaces after misc:lockdead_screen_delay has passed.\n        if (g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied())\n            return;\n    }\n\n    SRenderModifData RENDERMODIFDATA;\n    if (translate != Vector2D{0, 0})\n        RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, translate));\n    if UNLIKELY (scale != 1.f)\n        RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale));\n\n    if UNLIKELY (!RENDERMODIFDATA.modifs.empty())\n        m_renderPass.add(makeUnique<CRendererHintsPassElement>(CRendererHintsPassElement::SData{RENDERMODIFDATA}));\n\n    CScopeGuard x([&RENDERMODIFDATA] {\n        if (!RENDERMODIFDATA.modifs.empty()) {\n            g_pHyprRenderer->m_renderPass.add(makeUnique<CRendererHintsPassElement>(CRendererHintsPassElement::SData{SRenderModifData{}}));\n        }\n    });\n\n    if UNLIKELY (!pWorkspace) {\n        // allow rendering without a workspace. In this case, just render layers.\n\n        renderBackground(pMonitor);\n\n        for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) {\n            renderLayer(ls.lock(), pMonitor, time);\n        }\n\n        Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER);\n\n        for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) {\n            renderLayer(ls.lock(), pMonitor, time);\n        }\n\n        for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {\n            renderLayer(ls.lock(), pMonitor, time);\n        }\n\n        for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) {\n            renderLayer(ls.lock(), pMonitor, time);\n        }\n\n        return;\n    }\n\n    if LIKELY (!*PXPMODE) {\n        renderBackground(pMonitor);\n\n        for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) {\n            renderLayer(ls.lock(), pMonitor, time);\n        }\n\n        Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER);\n\n        for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) {\n            renderLayer(ls.lock(), pMonitor, time);\n        }\n    }\n\n    // pre window pass\n    if (preBlurQueued(pMonitor))\n        m_renderPass.add(makeUnique<CPreBlurElement>());\n\n    if UNLIKELY /* subjective? */ (pWorkspace->m_hasFullscreenWindow)\n        renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time);\n    else\n        renderWorkspaceWindows(pMonitor, pWorkspace, time);\n\n    // and then special\n    if UNLIKELY (pMonitor->m_specialFade->value() != 0.F) {\n        const auto SPECIALANIMPROGRS = pMonitor->m_specialFade->getCurveValue();\n        const bool ANIMOUT           = !pMonitor->m_activeSpecialWorkspace;\n\n        if (*PDIMSPECIAL != 0.f) {\n            CRectPassElement::SRectData data;\n            data.box   = {translate.x, translate.y, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale};\n            data.color = CHyprColor(0, 0, 0, *PDIMSPECIAL * (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS));\n\n            m_renderPass.add(makeUnique<CRectPassElement>(data));\n        }\n\n        if (*PBLURSPECIAL && *PBLUR) {\n            CRectPassElement::SRectData data;\n            data.box   = {translate.x, translate.y, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale};\n            data.color = CHyprColor(0, 0, 0, 0);\n            data.blur  = true;\n            data.blurA = (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS);\n\n            m_renderPass.add(makeUnique<CRectPassElement>(data));\n        }\n    }\n\n    // special\n    for (auto const& ws : g_pCompositor->getWorkspaces()) {\n        if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace)\n            continue;\n\n        if (ws->m_hasFullscreenWindow)\n            renderWorkspaceWindowsFullscreen(pMonitor, ws.lock(), time);\n        else\n            renderWorkspaceWindows(pMonitor, ws.lock(), time);\n    }\n\n    // pinned always above\n    for (auto const& w : g_pCompositor->m_windows) {\n        if (w->isHidden() && !w->m_isMapped && !w->m_fadingOut)\n            continue;\n\n        if (!w->m_pinned || !w->m_isFloating)\n            continue;\n\n        if (!shouldRenderWindow(w, pMonitor))\n            continue;\n\n        // render the bad boy\n        renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);\n    }\n\n    Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOWS);\n\n    // Render surfaces above windows for monitor\n    for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {\n        renderLayer(ls.lock(), pMonitor, time);\n    }\n\n    // Render IME popups\n    for (auto const& imep : g_pInputManager->m_relay.m_inputMethodPopups) {\n        renderIMEPopup(imep.get(), pMonitor, time);\n    }\n\n    for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) {\n        renderLayer(ls.lock(), pMonitor, time);\n    }\n\n    for (auto const& lsl : pMonitor->m_layerSurfaceLayers) {\n        for (auto const& ls : lsl) {\n            renderLayer(ls.lock(), pMonitor, time, true);\n        }\n    }\n\n    renderDragIcon(pMonitor, time);\n}\n\nSP<ITexture> IHyprRenderer::getBackground(PHLMONITOR pMonitor) {\n\n    if (m_backgroundResourceFailed)\n        return nullptr;\n\n    if (!m_backgroundResource) {\n        // queue the asset to be created\n        requestBackgroundResource();\n        return nullptr;\n    }\n\n    if (!m_backgroundResource->m_ready)\n        return nullptr;\n\n    Log::logger->log(Log::DEBUG, \"Creating a texture for BGTex\");\n    SP<ITexture> backgroundTexture = createTexture(m_backgroundResource->m_asset.cairoSurface->cairo());\n    if (!backgroundTexture->ok())\n        return nullptr;\n    Log::logger->log(Log::DEBUG, \"Background created for monitor {}\", pMonitor->m_name);\n\n    // clear the resource after we're done using it\n    g_pEventLoopManager->doLater([this] { m_backgroundResource.reset(); });\n\n    // set the animation to start for fading this background in nicely\n    pMonitor->m_backgroundOpacity->setValueAndWarp(0.F);\n    *pMonitor->m_backgroundOpacity = 1.F;\n\n    return backgroundTexture;\n}\n\nvoid IHyprRenderer::renderBackground(PHLMONITOR pMonitor) {\n    static auto PRENDERTEX       = CConfigValue<Hyprlang::INT>(\"misc:disable_hyprland_logo\");\n    static auto PBACKGROUNDCOLOR = CConfigValue<Hyprlang::INT>(\"misc:background_color\");\n    static auto PNOSPLASH        = CConfigValue<Hyprlang::INT>(\"misc:disable_splash_rendering\");\n\n    if (*PRENDERTEX /* inverted cfg flag */ || pMonitor->m_backgroundOpacity->isBeingAnimated())\n        m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)}));\n\n    if (!*PRENDERTEX) {\n        static auto PBACKGROUNDCOLOR = CConfigValue<Hyprlang::INT>(\"misc:background_color\");\n\n        if (!pMonitor->m_background)\n            pMonitor->m_background = getBackground(pMonitor);\n\n        if (!pMonitor->m_background)\n            m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)}));\n        else {\n            CTexPassElement::SRenderData data;\n            const double                 MONRATIO = m_renderData.pMonitor->m_transformedSize.x / m_renderData.pMonitor->m_transformedSize.y;\n            const double                 WPRATIO  = pMonitor->m_background->m_size.x / pMonitor->m_background->m_size.y;\n            Vector2D                     origin;\n            double                       scale = 1.0;\n\n            if (MONRATIO > WPRATIO) {\n                scale    = m_renderData.pMonitor->m_transformedSize.x / pMonitor->m_background->m_size.x;\n                origin.y = (m_renderData.pMonitor->m_transformedSize.y - pMonitor->m_background->m_size.y * scale) / 2.0;\n            } else {\n                scale    = m_renderData.pMonitor->m_transformedSize.y / pMonitor->m_background->m_size.y;\n                origin.x = (m_renderData.pMonitor->m_transformedSize.x - pMonitor->m_background->m_size.x * scale) / 2.0;\n            }\n\n            if (MONRATIO != WPRATIO)\n                m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)}));\n\n            data.box = {origin, pMonitor->m_background->m_size * scale};\n            data.a   = m_renderData.pMonitor->m_backgroundOpacity->value();\n            data.tex = pMonitor->m_background;\n            m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n        }\n    }\n\n    if (!*PNOSPLASH) {\n        auto monitorSize = pMonitor->m_transformedSize;\n        if (!pMonitor->m_splash)\n            pMonitor->m_splash = renderSplash([this, pMonitor](auto width, auto height, const auto DATA) { return createTexture(width, height, DATA); }, monitorSize.y / 76,\n                                              monitorSize.x, monitorSize.y);\n\n        if (pMonitor->m_splash) {\n            CTexPassElement::SRenderData data;\n            data.box = {{(monitorSize.x - pMonitor->m_splash->m_size.x) / 2.0, monitorSize.y * 0.98 - pMonitor->m_splash->m_size.y}, pMonitor->m_splash->m_size};\n            data.tex = pMonitor->m_splash;\n            m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n        }\n    }\n}\n\nvoid IHyprRenderer::requestBackgroundResource() {\n    if (m_backgroundResource)\n        return;\n\n    static auto PNOWALLPAPER    = CConfigValue<Hyprlang::INT>(\"misc:disable_hyprland_logo\");\n    static auto PFORCEWALLPAPER = CConfigValue<Hyprlang::INT>(\"misc:force_default_wallpaper\");\n\n    const auto  FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, sc<int64_t>(-1), sc<int64_t>(2));\n\n    if (*PNOWALLPAPER)\n        return;\n\n    static bool        once    = true;\n    static std::string texPath = \"wall\";\n\n    if (once) {\n        // get the adequate tex\n        if (FORCEWALLPAPER == -1) {\n            std::mt19937_64                 engine(time(nullptr));\n            std::uniform_int_distribution<> distribution(0, 2);\n\n            texPath += std::to_string(distribution(engine));\n        } else\n            texPath += std::to_string(std::clamp(*PFORCEWALLPAPER, sc<int64_t>(0), sc<int64_t>(2)));\n\n        texPath += \".png\";\n\n        texPath = resolveAssetPath(texPath);\n\n        once = false;\n    }\n\n    if (texPath.empty()) {\n        m_backgroundResourceFailed = true;\n        return;\n    }\n\n    m_backgroundResource = makeAtomicShared<Hyprgraphics::CImageResource>(texPath);\n\n    // doesn't have to be ASP as it's passed\n    SP<CMainLoopExecutor> executor = makeShared<CMainLoopExecutor>([this] {\n        for (const auto& m : g_pCompositor->m_monitors) {\n            damageMonitor(m);\n        }\n    });\n\n    m_backgroundResource->m_events.finished.listenStatic([executor] {\n        // this is in the worker thread.\n        executor->signal();\n    });\n\n    g_pAsyncResourceGatherer->enqueue(m_backgroundResource);\n}\n\nstd::string IHyprRenderer::resolveAssetPath(const std::string& filename) {\n    std::string fullPath;\n    for (auto& e : ASSET_PATHS) {\n        std::string     p = std::string{e} + \"/hypr/\" + filename;\n        std::error_code ec;\n        if (std::filesystem::exists(p, ec)) {\n            fullPath = p;\n            break;\n        } else\n            Log::logger->log(Log::DEBUG, \"resolveAssetPath: looking at {} unsuccessful: ec {}\", filename, ec.message());\n    }\n\n    if (fullPath.empty()) {\n        m_failedAssetsNo++;\n        Log::logger->log(Log::ERR, \"resolveAssetPath: looking for {} failed (no provider found)\", filename);\n        return \"\";\n    }\n\n    return fullPath;\n}\n\nSP<ITexture> IHyprRenderer::loadAsset(const std::string& filename) {\n\n    const std::string fullPath = resolveAssetPath(filename);\n\n    if (fullPath.empty())\n        return m_missingAssetTexture;\n\n    const auto CAIROSURFACE = cairo_image_surface_create_from_png(fullPath.c_str());\n\n    if (!CAIROSURFACE) {\n        m_failedAssetsNo++;\n        Log::logger->log(Log::ERR, \"loadAsset: failed to load {} (corrupt / inaccessible / not png)\", fullPath);\n        return m_missingAssetTexture;\n    }\n\n    auto tex = createTexture(CAIROSURFACE);\n\n    cairo_surface_destroy(CAIROSURFACE);\n\n    return tex;\n}\n\nSP<ITexture> IHyprRenderer::getBlurTexture(PHLMONITORREF pMonitor) {\n    if (!pMonitor->m_blurFB)\n        return nullptr;\n    return pMonitor->m_blurFB->getTexture();\n}\n\nbool IHyprRenderer::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow) {\n    static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>(\"decoration:blur:new_optimizations\");\n    static auto PBLURXRAY        = CConfigValue<Hyprlang::INT>(\"decoration:blur:xray\");\n\n    if (!getBlurTexture(m_renderData.pMonitor))\n        return false;\n\n    if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault())\n        return false;\n\n    if (pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 0)\n        return false;\n\n    if ((*PBLURNEWOPTIMIZE && pWindow && !pWindow->m_isFloating && !pWindow->onSpecialWorkspace()) || *PBLURXRAY)\n        return true;\n\n    if ((pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 1) || (pWindow && pWindow->m_ruleApplicator->xray().valueOrDefault()))\n        return true;\n\n    return false;\n}\n\nvoid IHyprRenderer::initMissingAssetTexture() {\n\n    const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512);\n    const auto CAIRO        = cairo_create(CAIROSURFACE);\n\n    cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_NONE);\n    cairo_save(CAIRO);\n    cairo_set_source_rgba(CAIRO, 0, 0, 0, 1);\n    cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE);\n    cairo_paint(CAIRO);\n    cairo_set_source_rgba(CAIRO, 1, 0, 1, 1);\n    cairo_rectangle(CAIRO, 256, 0, 256, 256);\n    cairo_fill(CAIRO);\n    cairo_rectangle(CAIRO, 0, 256, 256, 256);\n    cairo_fill(CAIRO);\n    cairo_restore(CAIRO);\n\n    cairo_surface_flush(CAIROSURFACE);\n\n    auto tex = createTexture(CAIROSURFACE);\n\n    cairo_surface_destroy(CAIROSURFACE);\n    cairo_destroy(CAIRO);\n\n    m_missingAssetTexture = tex;\n}\n\nvoid IHyprRenderer::initAssets() {\n    initMissingAssetTexture();\n\n    m_screencopyDeniedTexture = renderText(\"Permission denied to share screen\", Colors::WHITE, 20);\n}\n\nSP<ITexture> IHyprRenderer::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) {\n    static auto           FONT = CConfigValue<std::string>(\"misc:font_family\");\n\n    const auto            FONTFAMILY = fontFamily.empty() ? *FONT : fontFamily;\n    const auto            FONTSIZE   = pt;\n    const auto            COLOR      = col;\n\n    auto                  CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* arbitrary, just for size */);\n    auto                  CAIRO        = cairo_create(CAIROSURFACE);\n\n    PangoLayout*          layoutText = pango_cairo_create_layout(CAIRO);\n    PangoFontDescription* pangoFD    = pango_font_description_new();\n\n    pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str());\n    pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE);\n    pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);\n    pango_font_description_set_weight(pangoFD, sc<PangoWeight>(weight));\n    pango_layout_set_font_description(layoutText, pangoFD);\n\n    cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a);\n\n    int textW = 0, textH = 0;\n    pango_layout_set_text(layoutText, text.c_str(), -1);\n\n    if (maxWidth > 0) {\n        pango_layout_set_width(layoutText, maxWidth * PANGO_SCALE);\n        pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END);\n    }\n\n    pango_layout_get_size(layoutText, &textW, &textH);\n    textW /= PANGO_SCALE;\n    textH /= PANGO_SCALE;\n\n    pango_font_description_free(pangoFD);\n    g_object_unref(layoutText);\n    cairo_destroy(CAIRO);\n    cairo_surface_destroy(CAIROSURFACE);\n\n    CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH);\n    CAIRO        = cairo_create(CAIROSURFACE);\n\n    layoutText = pango_cairo_create_layout(CAIRO);\n    pangoFD    = pango_font_description_new();\n\n    pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str());\n    pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE);\n    pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);\n    pango_font_description_set_weight(pangoFD, sc<PangoWeight>(weight));\n    pango_layout_set_font_description(layoutText, pangoFD);\n    pango_layout_set_text(layoutText, text.c_str(), -1);\n\n    cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a);\n\n    cairo_move_to(CAIRO, 0, 0);\n    pango_cairo_show_layout(CAIRO, layoutText);\n\n    pango_font_description_free(pangoFD);\n    g_object_unref(layoutText);\n\n    cairo_surface_flush(CAIROSURFACE);\n\n    auto tex = createTexture(cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE), cairo_image_surface_get_data(CAIROSURFACE));\n\n    cairo_destroy(CAIRO);\n    cairo_surface_destroy(CAIROSURFACE);\n\n    return tex;\n}\n\nvoid IHyprRenderer::ensureLockTexturesRendered(bool load) {\n    static bool loaded = false;\n\n    if (loaded == load)\n        return;\n\n    loaded = load;\n\n    if (load) {\n        // this will cause a small hitch. I don't think we can do much, other than wasting VRAM and having this loaded all the time.\n        m_lockDeadTexture  = loadAsset(\"lockdead.png\");\n        m_lockDead2Texture = loadAsset(\"lockdead2.png\");\n\n        const auto VT = g_pCompositor->getVTNr();\n\n        m_lockTtyTextTexture = renderText(std::format(\"Running on tty {}\", VT.has_value() ? std::to_string(*VT) : \"unknown\"), CHyprColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true);\n    } else {\n        m_lockDeadTexture.reset();\n        m_lockDead2Texture.reset();\n        m_lockTtyTextTexture.reset();\n    }\n}\n\nvoid IHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry) {\n    TRACY_GPU_ZONE(\"RenderLockscreen\");\n\n    const bool LOCKED = g_pSessionLockManager->isSessionLocked();\n    if (!LOCKED) {\n        ensureLockTexturesRendered(false);\n        return;\n    }\n\n    const bool RENDERPRIMER = g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied();\n    if (RENDERPRIMER)\n        renderSessionLockPrimer(pMonitor);\n\n    const auto PSLS              = g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->m_id);\n    const bool RENDERLOCKMISSING = (PSLS.expired() || g_pSessionLockManager->clientDenied()) && g_pSessionLockManager->shallConsiderLockMissing();\n\n    ensureLockTexturesRendered(RENDERLOCKMISSING);\n\n    if (RENDERLOCKMISSING)\n        renderSessionLockMissing(pMonitor);\n    else if (PSLS) {\n        renderSessionLockSurface(PSLS, pMonitor, now);\n        g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->m_id);\n\n        // render layers and then their popups for abovelock rule\n        for (auto const& lsl : pMonitor->m_layerSurfaceLayers) {\n            for (auto const& ls : lsl) {\n                renderLayer(ls.lock(), pMonitor, now, false, true);\n            }\n        }\n        for (auto const& lsl : pMonitor->m_layerSurfaceLayers) {\n            for (auto const& ls : lsl) {\n                renderLayer(ls.lock(), pMonitor, now, true, true);\n            }\n        }\n    }\n}\n\nvoid IHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) {\n    static auto PSESSIONLOCKXRAY = CConfigValue<Hyprlang::INT>(\"misc:session_lock_xray\");\n    if (*PSESSIONLOCKXRAY)\n        return;\n\n    CRectPassElement::SRectData data;\n    data.color = CHyprColor(0, 0, 0, 1.f);\n    data.box   = CBox{{}, pMonitor->m_pixelSize};\n\n    m_renderPass.add(makeUnique<CRectPassElement>(data));\n}\n\nvoid IHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) {\n    const bool ANY_PRESENT = g_pSessionLockManager->anySessionLockSurfacesPresent();\n\n    // ANY_PRESENT: render image2, without instructions. Lock still \"alive\", unless texture dead\n    // else: render image, with instructions. Lock is gone.\n    CBox                         monbox = {{}, pMonitor->m_pixelSize};\n    CTexPassElement::SRenderData data;\n    data.tex = (ANY_PRESENT) ? m_lockDead2Texture : m_lockDeadTexture;\n    data.box = monbox;\n    data.a   = 1;\n\n    m_renderPass.add(makeUnique<CTexPassElement>(data));\n\n    if (!ANY_PRESENT && m_lockTtyTextTexture) {\n        // also render text for the tty number\n        CBox texbox = {{}, m_lockTtyTextTexture->m_size};\n        data.tex    = m_lockTtyTextTexture;\n        data.box    = texbox;\n\n        m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n    }\n}\n\nstatic std::optional<Vector2D> getSurfaceExpectedSize(PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface, PHLMONITOR pMonitor, bool main) {\n    const auto CAN_USE_WINDOW       = pWindow && main;\n    const auto WINDOW_SIZE_MISALIGN = CAN_USE_WINDOW && pWindow->getReportedSize() != pWindow->wlSurface()->resource()->m_current.size;\n\n    if (pSurface->m_current.viewport.hasDestination)\n        return (pSurface->m_current.viewport.destination * pMonitor->m_scale).round();\n\n    if (pSurface->m_current.viewport.hasSource)\n        return (pSurface->m_current.viewport.source.size() * pMonitor->m_scale).round();\n\n    if (WINDOW_SIZE_MISALIGN)\n        return (pSurface->m_current.size * pMonitor->m_scale).round();\n\n    if (CAN_USE_WINDOW)\n        return (pWindow->getReportedSize() * pMonitor->m_scale).round();\n\n    return std::nullopt;\n}\n\nvoid IHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize,\n                                          const Vector2D& projSizeUnscaled, bool fixMisalignedFSV1) {\n    if (!pWindow || !pWindow->m_isX11) {\n        static auto PEXPANDEDGES = CConfigValue<Hyprlang::INT>(\"render:expand_undersized_textures\");\n\n        Vector2D    uvTL;\n        Vector2D    uvBR = Vector2D(1, 1);\n\n        if (pSurface->m_current.viewport.hasSource) {\n            // we stretch it to dest. if no dest, to 1,1\n            Vector2D const& bufferSize   = pSurface->m_current.bufferSize;\n            auto const&     bufferSource = pSurface->m_current.viewport.source;\n\n            // calculate UV for the basic src_box. Assume dest == size. Scale to dest later\n            uvTL = Vector2D(bufferSource.x / bufferSize.x, bufferSource.y / bufferSize.y);\n            uvBR = Vector2D((bufferSource.x + bufferSource.width) / bufferSize.x, (bufferSource.y + bufferSource.height) / bufferSize.y);\n\n            if (uvBR.x < 0.01f || uvBR.y < 0.01f) {\n                uvTL = Vector2D();\n                uvBR = Vector2D(1, 1);\n            }\n        }\n\n        if (projSize != Vector2D{} && fixMisalignedFSV1) {\n            // instead of nearest_neighbor (we will repeat / skip)\n            // just cut off / expand surface\n            const Vector2D PIXELASUV   = Vector2D{1, 1} / pSurface->m_current.bufferSize;\n            const auto&    BUFFER_SIZE = pSurface->m_current.bufferSize;\n\n            // compute MISALIGN from the adjusted UV coordinates.\n            const Vector2D MISALIGNMENT = (uvBR - uvTL) * BUFFER_SIZE - projSize;\n\n            if (MISALIGNMENT != Vector2D{})\n                uvBR -= MISALIGNMENT * PIXELASUV;\n        } else {\n            // if the surface is smaller than our viewport, extend its edges.\n            // this will break if later on xdg geometry is hit, but we really try\n            // to let the apps know to NOT add CSD. Also if source is there.\n            // there is no way to fix this if that's the case\n            const auto MONITOR_WL_SCALE = std::ceil(pMonitor->m_scale);\n            const bool SCALE_UNAWARE    = pMonitor->m_scale != 1.f && (MONITOR_WL_SCALE == pSurface->m_current.scale || !pSurface->m_current.viewport.hasDestination);\n            const auto EXPECTED_SIZE    = getSurfaceExpectedSize(pWindow, pSurface, pMonitor, main).value_or((projSize * pMonitor->m_scale).round());\n\n            const auto RATIO = projSize / EXPECTED_SIZE;\n            if (!SCALE_UNAWARE || MONITOR_WL_SCALE == 1) {\n                if (*PEXPANDEDGES && !SCALE_UNAWARE && (RATIO.x > 1 || RATIO.y > 1)) {\n                    const auto FIX = RATIO.clamp(Vector2D{1, 1}, Vector2D{1000000, 1000000});\n                    uvBR           = uvBR * FIX;\n                }\n\n                // FIXME: probably do this for in anims on all views...\n                const auto SHOULD_SKIP = !pWindow || pWindow->m_animatingIn;\n                if (!SHOULD_SKIP && (RATIO.x < 1 || RATIO.y < 1)) {\n                    const auto FIX = RATIO.clamp(Vector2D{0.0001, 0.0001}, Vector2D{1, 1});\n                    uvBR           = uvBR * FIX;\n                }\n            }\n        }\n\n        m_renderData.primarySurfaceUVTopLeft     = uvTL;\n        m_renderData.primarySurfaceUVBottomRight = uvBR;\n\n        if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) {\n            // No special UV mods needed\n            m_renderData.primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n            m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);\n        }\n\n        if (!main || !pWindow)\n            return;\n\n        // FIXME: this doesn't work. We always set MAXIMIZED anyways, so this doesn't need to work, but it's problematic.\n\n        // CBox geom = pWindow->m_xdgSurface->m_current.geometry;\n\n        // // Adjust UV based on the xdg_surface geometry\n        // if (geom.x != 0 || geom.y != 0 || geom.w != 0 || geom.h != 0) {\n        //     const auto XPERC = geom.x / pSurface->m_current.size.x;\n        //     const auto YPERC = geom.y / pSurface->m_current.size.y;\n        //     const auto WPERC = (geom.x + geom.w ? geom.w : pSurface->m_current.size.x) / pSurface->m_current.size.x;\n        //     const auto HPERC = (geom.y + geom.h ? geom.h : pSurface->m_current.size.y) / pSurface->m_current.size.y;\n\n        //     const auto TOADDTL = Vector2D(XPERC * (uvBR.x - uvTL.x), YPERC * (uvBR.y - uvTL.y));\n        //     uvBR               = uvBR - Vector2D((1.0 - WPERC) * (uvBR.x - uvTL.x), (1.0 - HPERC) * (uvBR.y - uvTL.y));\n        //     uvTL               = uvTL + TOADDTL;\n        // }\n\n        m_renderData.primarySurfaceUVTopLeft     = uvTL;\n        m_renderData.primarySurfaceUVBottomRight = uvBR;\n\n        if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) {\n            // No special UV mods needed\n            m_renderData.primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n            m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);\n        }\n    } else {\n        m_renderData.primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n        m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);\n    }\n}\n\nbool IHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP<IHLBuffer> buffer, SP<IFramebuffer> fb, bool simple) {\n    m_renderPass.clear();\n    m_renderMode          = mode;\n    m_renderData.pMonitor = pMonitor;\n\n    if (simple)\n        setProjectionType(fb ? fb->m_size : buffer->m_texture->m_size);\n    else\n        setProjectionType(RPT_MONITOR);\n\n    if (!simple) {\n        const auto DRM_FORMAT = fb ? fb->m_drmFormat : pMonitor->m_output->state->state().drmFormat;\n\n        // ensure a framebuffer for the monitor exists\n        if (!m_renderData.pMonitor->m_offloadFB || m_renderData.pMonitor->m_offloadFB->m_size != pMonitor->m_pixelSize ||\n            DRM_FORMAT != m_renderData.pMonitor->m_offloadFB->m_drmFormat) {\n            if (!m_renderData.pMonitor->m_stencilTex || m_renderData.pMonitor->m_stencilTex->m_size != pMonitor->m_pixelSize)\n                m_renderData.pMonitor->m_stencilTex = createStencilTexture(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y);\n\n            m_renderData.pMonitor->m_offloadFB       = createFB(\"offload\");\n            m_renderData.pMonitor->m_mirrorFB        = createFB(\"mirror\");\n            m_renderData.pMonitor->m_mirrorSwapFB    = createFB(\"mirrorSwap\");\n            m_renderData.pMonitor->m_offMainFB       = createFB(\"offMain\");\n            m_renderData.pMonitor->m_monitorMirrorFB = createFB(\"monitorMirror\");\n            m_renderData.pMonitor->m_blurFB          = createFB(\"blur\");\n\n            // add stencil before FB allocation to avoid reallocs\n            m_renderData.pMonitor->m_offloadFB->addStencil(m_renderData.pMonitor->m_stencilTex);\n            m_renderData.pMonitor->m_mirrorFB->addStencil(m_renderData.pMonitor->m_stencilTex);\n            m_renderData.pMonitor->m_mirrorSwapFB->addStencil(m_renderData.pMonitor->m_stencilTex);\n            m_renderData.pMonitor->m_offMainFB->addStencil(m_renderData.pMonitor->m_stencilTex);\n\n            m_renderData.pMonitor->m_offloadFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);\n            m_renderData.pMonitor->m_mirrorFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);\n            m_renderData.pMonitor->m_mirrorSwapFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);\n            m_renderData.pMonitor->m_offMainFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);\n        }\n    }\n\n    const bool HAS_MIRROR_FB = g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB && g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->isAllocated();\n    const bool NEEDS_COPY_FB = needsACopyFB(g_pHyprRenderer->m_renderData.pMonitor.lock());\n\n    if (HAS_MIRROR_FB && !NEEDS_COPY_FB)\n        g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->release();\n    else if (!HAS_MIRROR_FB && NEEDS_COPY_FB && g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB)\n        g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->alloc(g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.x,\n                                                                         g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.y,\n                                                                         g_pHyprRenderer->m_renderData.pMonitor->m_output->state->state().drmFormat);\n\n    if (m_renderMode == RENDER_MODE_FULL_FAKE)\n        return beginFullFakeRenderInternal(pMonitor, damage, fb, simple);\n\n    int bufferAge = 0;\n\n    if (!buffer) {\n        m_currentBuffer = pMonitor->m_output->swapchain->next(&bufferAge);\n        if (!m_currentBuffer) {\n            Log::logger->log(Log::ERR, \"Failed to acquire swapchain buffer for {}\", pMonitor->m_name);\n            return false;\n        }\n    } else\n        m_currentBuffer = buffer;\n\n    initRender();\n\n    if (!initRenderBuffer(m_currentBuffer, pMonitor->m_output->state->state().drmFormat)) {\n        Log::logger->log(Log::ERR, \"failed to start a render pass for output {}, no RBO could be obtained\", pMonitor->m_name);\n        return false;\n    }\n\n    if (m_renderMode == RENDER_MODE_NORMAL) {\n        damage = pMonitor->m_damage.getBufferDamage(bufferAge);\n        pMonitor->m_damage.rotate();\n    }\n\n    const auto  res     = beginRenderInternal(pMonitor, damage, simple);\n    static bool initial = true;\n    if (initial) {\n        initAssets();\n        initial = false;\n    }\n\n    return res;\n}\n\nvoid IHyprRenderer::setDamage(const CRegion& damage_, std::optional<CRegion> finalDamage) {\n    m_renderData.damage.set(damage_);\n    m_renderData.finalDamage.set(finalDamage.value_or(damage_));\n}\n\nstatic Mat3x3 getMirrorProjection(PHLMONITORREF monitor) {\n    return Mat3x3::identity()\n        .translate(monitor->m_pixelSize / 2.0)\n        .transform(Math::wlTransformToHyprutils(monitor->m_transform))\n        .transform(Math::wlTransformToHyprutils(Math::invertTransform(monitor->m_mirrorOf->m_transform)))\n        .translate(-monitor->m_transformedSize / 2.0);\n}\n\nstatic Mat3x3 getFBProjection(PHLMONITORREF pMonitor, const Vector2D& size) {\n    if (pMonitor->m_transform == WL_OUTPUT_TRANSFORM_NORMAL)\n        return Mat3x3::identity();\n\n    const Vector2D tfmd = pMonitor->m_transform % 2 == 1 ? Vector2D{size.y, size.x} : size;\n    return Mat3x3::identity().translate(size / 2.0).transform(Math::wlTransformToHyprutils(pMonitor->m_transform)).translate(-tfmd / 2.0);\n}\n\nvoid IHyprRenderer::setProjectionType(const Vector2D& fbSize) {\n    m_renderData.fbSize = fbSize;\n    setProjectionType(RPT_FB);\n}\n\nvoid IHyprRenderer::setProjectionType(eRenderProjectionType projectionType) {\n    m_renderData.projectionType = projectionType;\n    switch (projectionType) {\n        case RPT_MONITOR: m_renderData.targetProjection = m_renderData.pMonitor->getTransformMatrix(); break;\n        case RPT_MIRROR: m_renderData.targetProjection = getMirrorProjection(m_renderData.pMonitor); break;\n        case RPT_FB: m_renderData.targetProjection = getFBProjection(m_renderData.pMonitor, m_renderData.fbSize); break;\n        case RPT_EXPORT: m_renderData.targetProjection = Mat3x3::identity(); break;\n        default: UNREACHABLE();\n    }\n}\n\nMat3x3 IHyprRenderer::getBoxProjection(const CBox& box, std::optional<eTransform> transform) {\n    return m_renderData.targetProjection.projectBox(\n        box, transform.value_or(Math::wlTransformToHyprutils(Math::invertTransform(!monitorTransformEnabled() ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform))),\n        box.rot);\n}\n\nMat3x3 IHyprRenderer::projectBoxToTarget(const CBox& box, std::optional<eTransform> transform) {\n    return (m_renderData.projectionType == RPT_EXPORT ? Mat3x3::outputProjection(m_renderData.fbSize, HYPRUTILS_TRANSFORM_NORMAL) : m_renderData.pMonitor->getScaleMatrix())\n        .copy()\n        .multiply(getBoxProjection(box, transform));\n}\n\nSP<ITexture> IHyprRenderer::blurMainFramebuffer(float a, CRegion* originalDamage) {\n    if (!m_renderData.currentFB->getTexture()) {\n        Log::logger->log(Log::ERR, \"BUG THIS: null fb texture while attempting to blur main fb?! (introspection off?!)\");\n        return m_renderData.pMonitor->m_mirrorFB->getTexture(); // return something to sample from at least\n    }\n\n    return blurFramebuffer(m_renderData.currentFB, a, originalDamage);\n}\n\nvoid IHyprRenderer::preBlurForCurrentMonitor(CRegion* fakeDamage) {\n\n    const auto blurredTex = blurMainFramebuffer(1, fakeDamage);\n\n    // render onto blurFB\n    if (!m_renderData.pMonitor->m_blurFB)\n        return;\n    m_renderData.pMonitor->m_blurFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, m_renderData.pMonitor->m_output->state->state().drmFormat);\n    m_renderData.pMonitor->m_blurFB->bind();\n\n    draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), {});\n\n    pushMonitorTransformEnabled(true);\n\n    draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{\n             .tex    = blurredTex,\n             .box    = CBox{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y},\n             .damage = *fakeDamage,\n         }),\n         *fakeDamage); // .noAA = true\n\n    popMonitorTransformEnabled();\n\n    m_renderData.currentFB->bind();\n}\n\nstatic bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {\n    // might be too strict\n    return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||\n            imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22) &&\n        (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||\n         targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG);\n}\n\nstatic bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {\n    // might be too strict\n    return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||\n            imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG) &&\n        (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||\n         targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);\n}\n\nSCMSettings IHyprRenderer::getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,\n                                         SP<CWLSurfaceResource> surface, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) {\n    const auto                          sdrEOTF = NTransferFunction::fromConfig();\n    NColorManagement::eTransferFunction srcTF;\n\n    if (m_renderData.surface.valid()) {\n        if (m_renderData.surface->m_colorManagement.valid()) {\n            if (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && imageDescription->value().transferFunction == NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB)\n                srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22;\n            else\n                srcTF = imageDescription->value().transferFunction;\n        } else if (sdrEOTF == NTransferFunction::TF_SRGB)\n            srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB;\n        else if (sdrEOTF == NTransferFunction::TF_GAMMA22 || sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22)\n            srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22;\n        else\n            srcTF = imageDescription->value().transferFunction;\n    } else\n        srcTF = imageDescription->value().transferFunction;\n\n    const bool  needsSDRmod     = modifySDR && isSDR2HDR(imageDescription->value(), targetImageDescription->value());\n    const bool  needsHDRmod     = !needsSDRmod && isHDR2SDR(imageDescription->value(), targetImageDescription->value());\n    const float maxLuminance    = needsHDRmod ?\n        imageDescription->value().getTFMaxLuminance(-1) :\n        (imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference);\n    const auto  dstMaxLuminance = targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000;\n\n    auto        matrix = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries());\n    auto        toXYZ  = targetImageDescription->getPrimaries()->value().toXYZ();\n\n    const bool needsMod = (imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) &&\n        targetImageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ &&\n        ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) ||\n         (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f));\n\n    return {\n        .sourceTF        = srcTF,\n        .targetTF        = targetImageDescription->value().transferFunction,\n        .srcTFRange      = {.min = imageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),\n                            .max = imageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)},\n        .dstTFRange      = {.min = targetImageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),\n                            .max = targetImageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)},\n        .srcRefLuminance = imageDescription->value().luminances.reference,\n        .dstRefLuminance = targetImageDescription->value().luminances.reference,\n        .convertMatrix   = matrix.mat(),\n\n        .needsTonemap            = maxLuminance >= dstMaxLuminance * 1.01,\n        .maxLuminance            = maxLuminance * targetImageDescription->value().luminances.reference / imageDescription->value().luminances.reference,\n        .dstMaxLuminance         = dstMaxLuminance,\n        .dstPrimaries2XYZ        = toXYZ.mat(),\n        .needsSDRmod             = needsMod,\n        .sdrSaturation           = needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f,\n        .sdrBrightnessMultiplier = needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f,\n    };\n}\n\nvoid IHyprRenderer::renderMirrored() {\n    auto         monitor  = m_renderData.pMonitor;\n    auto         mirrored = monitor->m_mirrorOf;\n\n    const double scale  = std::min(monitor->m_transformedSize.x / mirrored->m_transformedSize.x, monitor->m_transformedSize.y / mirrored->m_transformedSize.y);\n    CBox         monbox = {0, 0, mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale};\n\n    // transform box as it will be drawn on a transformed projection\n    monbox.transform(Math::wlTransformToHyprutils(mirrored->m_transform), mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale);\n\n    monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2;\n    monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2;\n\n    if (!monitor->m_monitorMirrorFB)\n        monitor->m_monitorMirrorFB = createFB(\"monitorMirror\");\n\n    const auto PFB = mirrored->m_monitorMirrorFB;\n    if (!PFB || !PFB->isAllocated() || !PFB->getTexture())\n        return;\n\n    m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}));\n\n    CTexPassElement::SRenderData data;\n    data.tex                 = PFB->getTexture();\n    data.box                 = monbox;\n    data.useMirrorProjection = true;\n\n    m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n\nvoid IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {\n    static std::chrono::high_resolution_clock::time_point renderStart        = std::chrono::high_resolution_clock::now();\n    static std::chrono::high_resolution_clock::time_point renderStartOverlay = std::chrono::high_resolution_clock::now();\n    static std::chrono::high_resolution_clock::time_point endRenderOverlay   = std::chrono::high_resolution_clock::now();\n\n    static auto                                           PDEBUGOVERLAY       = CConfigValue<Hyprlang::INT>(\"debug:overlay\");\n    static auto                                           PDAMAGETRACKINGMODE = CConfigValue<Hyprlang::INT>(\"debug:damage_tracking\");\n    static auto                                           PDAMAGEBLINK        = CConfigValue<Hyprlang::INT>(\"debug:damage_blink\");\n    static auto                                           PSOLDAMAGE          = CConfigValue<Hyprlang::INT>(\"debug:render_solitary_wo_damage\");\n    static auto                                           PVFR                = CConfigValue<Hyprlang::INT>(\"misc:vfr\");\n\n    static int                                            damageBlinkCleanup = 0; // because double-buffered\n\n    const float                                           ZOOMFACTOR = pMonitor->m_cursorZoom->value();\n\n    if (pMonitor->m_pixelSize.x < 1 || pMonitor->m_pixelSize.y < 1) {\n        Log::logger->log(Log::ERR, \"Refusing to render a monitor because of an invalid pixel size: {}\", pMonitor->m_pixelSize);\n        return;\n    }\n\n    if (!*PDAMAGEBLINK)\n        damageBlinkCleanup = 0;\n\n    if (*PDEBUGOVERLAY == 1) {\n        renderStart = std::chrono::high_resolution_clock::now();\n        g_pDebugOverlay->frameData(pMonitor);\n    }\n\n    if (!g_pCompositor->m_sessionActive)\n        return;\n\n    if (g_pAnimationManager)\n        g_pAnimationManager->frameTick();\n\n    if (pMonitor->m_id == m_mostHzMonitor->m_id ||\n        *PVFR == 1) { // unfortunately with VFR we don't have the guarantee mostHz is going to be updated all the time, so we have to ignore that\n\n        g_pConfigManager->dispatchExecOnce(); // We exec-once when at least one monitor starts refreshing, meaning stuff has init'd\n\n        if (g_pConfigManager->m_wantsMonitorReload)\n            g_pConfigManager->performMonitorReload();\n    }\n\n    if (pMonitor->m_scheduledRecalc) {\n        pMonitor->m_scheduledRecalc = false;\n        if (pMonitor->m_activeWorkspace) // might be missing (mirror)\n            pMonitor->m_activeWorkspace->m_space->recalculate();\n    }\n\n    if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0)\n        return;\n\n    // tearing and DS first\n    bool shouldTear = pMonitor->updateTearing();\n\n    if (pMonitor->attemptDirectScanout()) {\n        pMonitor->m_directScanoutIsActive = true;\n        return;\n    } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) {\n        Log::logger->log(Log::DEBUG, \"Left a direct scanout.\");\n        pMonitor->m_lastScanout.reset();\n        pMonitor->m_directScanoutIsActive = false;\n\n        // reset DRM format, but only if needed since it might modeset\n        if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat)\n            pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat);\n\n        pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat;\n    }\n\n    Event::bus()->m_events.render.pre.emit(pMonitor);\n\n    const auto NOW = Time::steadyNow();\n\n    // check the damage\n    bool hasChanged = pMonitor->m_output->needsFrame || pMonitor->m_damage.hasChanged();\n\n    if (!hasChanged && *PDAMAGETRACKINGMODE != DAMAGE_TRACKING_NONE && pMonitor->m_forceFullFrames == 0 && damageBlinkCleanup == 0)\n        return;\n\n    if (*PDAMAGETRACKINGMODE == -1) {\n        Log::logger->log(Log::CRIT, \"Damage tracking mode -1 ????\");\n        return;\n    }\n\n    Event::bus()->m_events.render.stage.emit(RENDER_PRE);\n\n    pMonitor->m_renderingActive = true;\n\n    // we need to cleanup fading out when rendering the appropriate context\n    g_pCompositor->cleanupFadingOut(pMonitor->m_id);\n\n    // TODO: this is getting called with extents being 0,0,0,0 should it be?\n    // potentially can save on resources.\n\n    TRACY_GPU_ZONE(\"Render\");\n\n    static bool zoomLock = false;\n    if (zoomLock && ZOOMFACTOR == 1.f) {\n        g_pPointerManager->unlockSoftwareAll();\n        zoomLock = false;\n    } else if (!zoomLock && ZOOMFACTOR != 1.f) {\n        g_pPointerManager->lockSoftwareAll();\n        zoomLock = true;\n    }\n\n    if (pMonitor == g_pCompositor->getMonitorFromCursor())\n        m_renderData.mouseZoomFactor = std::clamp(ZOOMFACTOR, 1.f, INFINITY);\n    else\n        m_renderData.mouseZoomFactor = 1.f;\n\n    if (pMonitor->m_zoomAnimProgress->value() != 1) {\n        m_renderData.mouseZoomFactor    = 2.0 - pMonitor->m_zoomAnimProgress->value(); // 2x zoom -> 1x zoom\n        m_renderData.mouseZoomUseMouse  = false;\n        m_renderData.useNearestNeighbor = false;\n    }\n\n    CRegion damage, finalDamage;\n    if (!beginRender(pMonitor, damage, RENDER_MODE_NORMAL)) {\n        Log::logger->log(Log::ERR, \"renderer: couldn't beginRender()!\");\n        return;\n    }\n\n    // if we have no tracking or full tracking, invalidate the entire monitor\n    if (*PDAMAGETRACKINGMODE == DAMAGE_TRACKING_NONE || *PDAMAGETRACKINGMODE == DAMAGE_TRACKING_MONITOR || pMonitor->m_forceFullFrames > 0 || damageBlinkCleanup > 0)\n        damage = {0, 0, sc<int>(pMonitor->m_transformedSize.x) * 10, sc<int>(pMonitor->m_transformedSize.y) * 10};\n\n    finalDamage = damage;\n\n    // update damage in renderdata as we modified it\n    setDamage(damage, finalDamage);\n\n    if (pMonitor->m_forceFullFrames > 0) {\n        pMonitor->m_forceFullFrames -= 1;\n        if (pMonitor->m_forceFullFrames > 10)\n            pMonitor->m_forceFullFrames = 0;\n    }\n\n    Event::bus()->m_events.render.stage.emit(RENDER_BEGIN);\n\n    bool renderCursor = true;\n\n    if (pMonitor->m_solitaryClient && (!finalDamage.empty() || *PSOLDAMAGE))\n        renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */);\n    else if (!finalDamage.empty()) {\n        if (pMonitor->isMirror()) {\n            blend(false);\n            renderMirrored();\n            blend(true);\n            Event::bus()->m_events.render.stage.emit(RENDER_POST_MIRROR);\n            renderCursor = false;\n        } else {\n            CBox renderBox = {0, 0, sc<int>(pMonitor->m_pixelSize.x), sc<int>(pMonitor->m_pixelSize.y)};\n            renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox);\n\n            renderLockscreen(pMonitor, NOW, renderBox);\n\n            if (pMonitor == Desktop::focusState()->monitor()) {\n                g_pHyprNotificationOverlay->draw(pMonitor);\n                g_pHyprError->draw();\n            }\n\n            // for drawing the debug overlay\n            if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) {\n                renderStartOverlay = std::chrono::high_resolution_clock::now();\n                g_pDebugOverlay->draw();\n                endRenderOverlay = std::chrono::high_resolution_clock::now();\n            }\n\n            if (*PDAMAGEBLINK && damageBlinkCleanup == 0) {\n                CRectPassElement::SRectData data;\n                data.box   = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};\n                data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0);\n                m_renderPass.add(makeUnique<CRectPassElement>(data));\n                damageBlinkCleanup = 1;\n            } else if (*PDAMAGEBLINK) {\n                damageBlinkCleanup++;\n                if (damageBlinkCleanup > 3)\n                    damageBlinkCleanup = 0;\n            }\n        }\n    } else if (!pMonitor->isMirror()) {\n        sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW);\n        if (pMonitor->m_activeSpecialWorkspace)\n            sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeSpecialWorkspace, NOW);\n    }\n\n    renderCursor = renderCursor && shouldRenderCursor();\n\n    if (renderCursor) {\n        TRACY_GPU_ZONE(\"RenderCursor\");\n        g_pPointerManager->renderSoftwareCursorsFor(pMonitor->m_self.lock(), NOW, m_renderData.damage);\n    }\n\n    if (pMonitor->m_dpmsBlackOpacity->value() != 0.F) {\n        // render the DPMS black if we are animating\n        CRectPassElement::SRectData data;\n        data.box   = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};\n        data.color = Colors::BLACK.modifyA(pMonitor->m_dpmsBlackOpacity->value());\n        m_renderPass.add(makeUnique<CRectPassElement>(data));\n    }\n\n    Event::bus()->m_events.render.stage.emit(RENDER_LAST_MOMENT);\n\n    endRender();\n\n    TRACY_GPU_COLLECT;\n\n    CRegion    frameDamage{m_renderData.damage};\n\n    const auto TRANSFORM = Math::invertTransform(pMonitor->m_transform);\n    frameDamage.transform(Math::wlTransformToHyprutils(TRANSFORM), pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y);\n\n    if (*PDAMAGETRACKINGMODE == DAMAGE_TRACKING_NONE || *PDAMAGETRACKINGMODE == DAMAGE_TRACKING_MONITOR)\n        frameDamage.add(0, 0, sc<int>(pMonitor->m_transformedSize.x), sc<int>(pMonitor->m_transformedSize.y));\n\n    if (*PDAMAGEBLINK)\n        frameDamage.add(damage);\n\n    if (!pMonitor->m_mirrors.empty())\n        damageMirrorsWith(pMonitor, frameDamage);\n\n    pMonitor->m_renderingActive = false;\n\n    Event::bus()->m_events.render.stage.emit(RENDER_POST);\n\n    pMonitor->m_output->state->addDamage(frameDamage);\n    pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :\n                                                                Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);\n\n    if (commit)\n        commitPendingAndDoExplicitSync(pMonitor);\n\n    if (shouldTear)\n        pMonitor->m_tearingState.busy = true;\n\n    if (*PDAMAGEBLINK || *PVFR == 0 || pMonitor->m_pendingFrame)\n        g_pCompositor->scheduleFrameForMonitor(pMonitor, Aquamarine::IOutput::AQ_SCHEDULE_RENDER_MONITOR);\n\n    pMonitor->m_pendingFrame = false;\n\n    if (*PDEBUGOVERLAY == 1) {\n        const float durationUs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000.f;\n        g_pDebugOverlay->renderData(pMonitor, durationUs);\n\n        if (pMonitor == g_pCompositor->m_monitors.front()) {\n            const float noOverlayUs = durationUs - std::chrono::duration_cast<std::chrono::nanoseconds>(endRenderOverlay - renderStartOverlay).count() / 1000.f;\n            g_pDebugOverlay->renderDataNoOverlay(pMonitor, noOverlayUs);\n        } else\n            g_pDebugOverlay->renderDataNoOverlay(pMonitor, durationUs);\n    }\n}\n\nstatic const hdr_output_metadata NO_HDR_METADATA = {.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}};\n\nstatic hdr_output_metadata       createHDRMetadata(SImageDescription settings, SP<CMonitor> monitor) {\n    uint8_t eotf = 0;\n    switch (settings.transferFunction) {\n        case CM_TRANSFER_FUNCTION_GAMMA22:\n        case CM_TRANSFER_FUNCTION_SRGB: eotf = 0; break; // used to send primaries and luminances to AQ. ignored for now\n        case CM_TRANSFER_FUNCTION_ST2084_PQ: eotf = 2; break;\n        case CM_TRANSFER_FUNCTION_EXT_LINEAR:\n            eotf = 2;\n            break; // should be Windows scRGB\n        // case CM_TRANSFER_FUNCTION_HLG: eotf = 3; break; TODO check display capabilities first\n        default: return NO_HDR_METADATA; // empty metadata for SDR\n    }\n\n    const auto toNits  = [](uint32_t value) { return sc<uint16_t>(std::round(value)); };\n    const auto to16Bit = [](float value) { return sc<uint16_t>(std::round(value * 50000)); };\n\n    auto       colorimetry = settings.getPrimaries();\n    auto       luminances  = settings.masteringLuminances.max > 0 ? settings.masteringLuminances :\n                                                                    (settings.luminances != SImageDescription::SPCLuminances{} ?\n                                                                         SImageDescription::SPCMasteringLuminances{.min = settings.luminances.min, .max = settings.luminances.max} :\n                                                                         SImageDescription::SPCMasteringLuminances{.min = monitor->minLuminance(), .max = monitor->maxLuminance(10000)});\n\n    Log::logger->log(Log::TRACE, \"ColorManagement primaries {},{} {},{} {},{} {},{}\", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y,\n                     colorimetry.blue.x, colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y);\n    Log::logger->log(Log::TRACE, \"ColorManagement min {}, max {}, cll {}, fall {}\", luminances.min, luminances.max, settings.maxCLL, settings.maxFALL);\n    return hdr_output_metadata{\n        .metadata_type = 0,\n        .hdmi_metadata_type1 =\n            hdr_metadata_infoframe{\n                .eotf          = eotf,\n                .metadata_type = 0,\n                .display_primaries =\n                    {\n                        {.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)},\n                        {.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)},\n                        {.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)},\n                    },\n                .white_point                     = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)},\n                .max_display_mastering_luminance = toNits(luminances.max),\n                .min_display_mastering_luminance = toNits(luminances.min * 10000),\n                .max_cll                         = toNits(settings.maxCLL > 0 ? settings.maxCLL : monitor->maxCLL()),\n                .max_fall                        = toNits(settings.maxFALL > 0 ? settings.maxFALL : monitor->maxFALL()),\n            },\n    };\n}\n\nbool IHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {\n    static auto PCT        = CConfigValue<Hyprlang::INT>(\"render:send_content_type\");\n    static auto PPASS      = CConfigValue<Hyprlang::INT>(\"render:cm_fs_passthrough\");\n    static auto PAUTOHDR   = CConfigValue<Hyprlang::INT>(\"render:cm_auto_hdr\");\n    static auto PNONSHADER = CConfigValue<Hyprlang::INT>(\"render:non_shader_cm\");\n\n    const bool  configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR);\n    bool        wantHDR       = configuredHDR;\n\n    const auto  FS_WINDOW = pMonitor->getFullscreenWindow();\n\n    if (pMonitor->supportsHDR()) {\n        // HDR metadata determined by\n        // HDR scRGB - monitor settings\n        // HDR PQ surface & DS is active - surface settings\n        // PPASS = 0 monitor settings\n        // PPASS = 1\n        //           windowed: monitor settings\n        //           fullscreen surface: surface settings FIXME: fullscreen SDR surface passthrough - pass degamma, gamma if needed\n        // PPASS = 2\n        //           windowed: monitor settings\n        //           fullscreen SDR surface: monitor settings\n        //           fullscreen HDR surface: surface settings\n\n        bool hdrIsHandled = false;\n        if (FS_WINDOW) {\n            const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();\n            const auto SURF      = ROOT_SURF->findWithCM();\n\n            // we have a surface with image description\n            if (SURF && SURF->m_colorManagement.valid() && SURF->m_colorManagement->hasImageDescription()) {\n                const bool surfaceIsHDR = SURF->m_colorManagement->isHDR();\n                if (!SURF->m_colorManagement->isWindowsScRGB() && (*PPASS == 1 || ((*PPASS == 2 || !pMonitor->m_lastScanout.expired()) && surfaceIsHDR))) {\n                    // passthrough\n                    bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != FS_WINDOW || pMonitor->m_needsHDRupdate;\n                    if (SURF->m_colorManagement->needsHdrMetadataUpdate()) {\n                        Log::logger->log(Log::INFO, \"[CM] Recreating HDR metadata for surface\");\n                        SURF->m_colorManagement->setHDRMetadata(createHDRMetadata(SURF->m_colorManagement->imageDescription(), pMonitor));\n                    }\n                    if (needsHdrMetadataUpdate) {\n                        Log::logger->log(Log::INFO, \"[CM] Updating HDR metadata from surface\");\n                        pMonitor->m_output->state->setHDRMetadata(SURF->m_colorManagement->hdrMetadata());\n                    }\n                    hdrIsHandled               = true;\n                    pMonitor->m_needsHDRupdate = false;\n                } else if (*PAUTOHDR && surfaceIsHDR)\n                    wantHDR = true; // auto-hdr: hdr on\n            }\n        }\n\n        if (!hdrIsHandled) {\n            if (pMonitor->inHDR() != wantHDR) {\n                if (*PAUTOHDR && !(pMonitor->inHDR() && configuredHDR)) {\n                    // modify or restore monitor image description for auto-hdr\n                    // FIXME ok for now, will need some other logic if monitor image description can be modified some other way\n                    const auto targetCM      = wantHDR ? (*PAUTOHDR == 2 ? NCMType::CM_HDR_EDID : NCMType::CM_HDR) : pMonitor->m_cmType;\n                    const auto targetSDREOTF = pMonitor->m_sdrEotf;\n                    Log::logger->log(Log::INFO, \"[CM] Auto HDR: changing monitor cm to {}\", sc<uint8_t>(targetCM));\n                    pMonitor->applyCMType(targetCM, targetSDREOTF);\n                    pMonitor->m_previousFSWindow.reset(); // trigger CTM update\n                }\n                Log::logger->log(Log::INFO, wantHDR ? \"[CM] Updating HDR metadata from monitor\" : \"[CM] Restoring SDR mode\");\n                pMonitor->m_output->state->setHDRMetadata(wantHDR ? createHDRMetadata(pMonitor->m_imageDescription->value(), pMonitor) : NO_HDR_METADATA);\n            }\n            pMonitor->m_needsHDRupdate = true;\n        }\n    }\n\n    const bool needsWCG = pMonitor->wantsWideColor();\n    if (pMonitor->m_output->state->state().wideColorGamut != needsWCG) {\n        Log::logger->log(Log::TRACE, \"Setting wide color gamut {}\", needsWCG ? \"on\" : \"off\");\n        pMonitor->m_output->state->setWideColorGamut(needsWCG);\n\n        // FIXME do not trust enabled10bit, auto switch to 10bit and back if needed\n        if (needsWCG && !pMonitor->m_enabled10bit) {\n            Log::logger->log(Log::WARN, \"Wide color gamut is enabled but the display is not in 10bit mode\");\n            static bool shown = false;\n            if (!shown) {\n                g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{\"name\", pMonitor->m_name}}), CHyprColor{}, 15000,\n                                                            ICON_WARNING);\n                shown = true;\n            }\n        }\n    }\n\n    if (*PCT)\n        pMonitor->m_output->state->setContentType(NContentType::toDRM(FS_WINDOW ? FS_WINDOW->getContentType() : CONTENT_TYPE_NONE));\n\n    if (FS_WINDOW != pMonitor->m_previousFSWindow || (!FS_WINDOW && pMonitor->m_noShaderCTM)) {\n        if (*PNONSHADER == CM_NS_IGNORE || !FS_WINDOW || !pMonitor->needsCM() || !pMonitor->canNoShaderCM() ||\n            (*PNONSHADER == CM_NS_ONDEMAND && pMonitor->m_lastScanout.expired() && *PPASS != 1)) {\n            if (pMonitor->m_noShaderCTM) {\n                Log::logger->log(Log::INFO, \"[CM] No fullscreen CTM, restoring previous one\");\n                pMonitor->m_noShaderCTM = false;\n                pMonitor->m_ctmUpdated  = true;\n            }\n        } else {\n            const auto FS_DESC = pMonitor->getFSImageDescription();\n            if (FS_DESC.has_value()) {\n                Log::logger->log(Log::INFO, \"[CM] Updating fullscreen CTM\");\n                pMonitor->m_noShaderCTM               = true;\n                auto                       conversion = FS_DESC.value()->getPrimaries()->convertMatrix(pMonitor->m_imageDescription->getPrimaries());\n                const auto                 mat        = conversion.mat();\n                const std::array<float, 9> CTM        = {\n                    mat[0][0], mat[0][1], mat[0][2], //\n                    mat[1][0], mat[1][1], mat[1][2], //\n                    mat[2][0], mat[2][1], mat[2][2], //\n                };\n                pMonitor->m_output->state->setCTM(CTM);\n            }\n        }\n    }\n\n    if (pMonitor->m_ctmUpdated && !pMonitor->m_noShaderCTM) {\n        pMonitor->m_ctmUpdated = false;\n        pMonitor->m_output->state->setCTM(pMonitor->m_ctm);\n    }\n\n    pMonitor->m_previousFSWindow = FS_WINDOW;\n\n    bool ok = pMonitor->m_state.commit();\n    if (!ok) {\n        if (pMonitor->m_inFence.isValid()) {\n            Log::logger->log(Log::TRACE, \"Monitor state commit failed, retrying without a fence\");\n            pMonitor->m_output->state->resetExplicitFences();\n            ok = pMonitor->m_state.commit();\n        }\n\n        if (!ok) {\n            Log::logger->log(Log::TRACE, \"Monitor state commit failed\");\n            // rollback the buffer to avoid writing to the front buffer that is being\n            // displayed\n            pMonitor->m_output->swapchain->rollback();\n            pMonitor->m_damage.damageEntire();\n        }\n    }\n\n    return ok;\n}\n\nvoid IHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry) {\n    Vector2D translate = {geometry.x, geometry.y};\n    float    scale     = sc<float>(geometry.width) / pMonitor->m_pixelSize.x;\n\n    TRACY_GPU_ZONE(\"RenderWorkspace\");\n\n    if (!DELTALESSTHAN(sc<double>(geometry.width) / sc<double>(geometry.height), pMonitor->m_pixelSize.x / pMonitor->m_pixelSize.y, 0.01)) {\n        Log::logger->log(Log::ERR, \"Ignoring geometry in renderWorkspace: aspect ratio mismatch\");\n        scale     = 1.f;\n        translate = Vector2D{};\n    }\n\n    renderAllClientsForWorkspace(pMonitor, pWorkspace, now, translate, scale);\n}\n\nvoid IHyprRenderer::sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now) {\n    for (const auto& view : Desktop::View::getViewsForWorkspace(pWorkspace)) {\n        if (!view->aliveAndVisible())\n            continue;\n\n        view->wlSurface()->resource()->frame(now);\n    }\n}\n\nvoid IHyprRenderer::setSurfaceScanoutMode(SP<CWLSurfaceResource> surface, PHLMONITOR monitor) {\n    if (!PROTO::linuxDma)\n        return;\n\n    PROTO::linuxDma->updateScanoutTranche(surface, monitor);\n}\n\n// taken from Sway.\n// this is just too much of a spaghetti for me to understand\nstatic void applyExclusive(CBox& usableArea, uint32_t anchor, int32_t exclusive, uint32_t exclusiveEdge, int32_t marginTop, int32_t marginRight, int32_t marginBottom,\n                           int32_t marginLeft) {\n    if (exclusive <= 0) {\n        return;\n    }\n    struct {\n        uint32_t singular_anchor;\n        uint32_t anchor_triplet;\n        double*  positive_axis;\n        double*  negative_axis;\n        int      margin;\n    } edges[] = {\n        // Top\n        {\n            .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,\n            .anchor_triplet  = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,\n            .positive_axis   = &usableArea.y,\n            .negative_axis   = &usableArea.height,\n            .margin          = marginTop,\n        },\n        // Bottom\n        {\n            .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,\n            .anchor_triplet  = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,\n            .positive_axis   = nullptr,\n            .negative_axis   = &usableArea.height,\n            .margin          = marginBottom,\n        },\n        // Left\n        {\n            .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT,\n            .anchor_triplet  = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,\n            .positive_axis   = &usableArea.x,\n            .negative_axis   = &usableArea.width,\n            .margin          = marginLeft,\n        },\n        // Right\n        {\n            .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT,\n            .anchor_triplet  = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,\n            .positive_axis   = nullptr,\n            .negative_axis   = &usableArea.width,\n            .margin          = marginRight,\n        },\n    };\n    for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) {\n        if ((exclusiveEdge == edges[i].singular_anchor || anchor == edges[i].singular_anchor || anchor == edges[i].anchor_triplet) && exclusive + edges[i].margin > 0) {\n            if (edges[i].positive_axis) {\n                *edges[i].positive_axis += exclusive + edges[i].margin;\n            }\n            if (edges[i].negative_axis) {\n                *edges[i].negative_axis -= exclusive + edges[i].margin;\n            }\n            break;\n        }\n    }\n}\n\nvoid IHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vector<PHLLSREF>& layerSurfaces, bool exclusiveZone, CBox* usableArea) {\n    CBox full_area = {pMonitor->m_position.x, pMonitor->m_position.y, pMonitor->m_size.x, pMonitor->m_size.y};\n\n    for (auto const& ls : layerSurfaces) {\n        if (!ls || ls->m_fadingOut || ls->m_readyToDelete || !ls->m_layerSurface || ls->m_noProcess)\n            continue;\n\n        const auto PLAYER = ls->m_layerSurface;\n        const auto PSTATE = &PLAYER->m_current;\n        if (exclusiveZone != (PSTATE->exclusive > 0))\n            continue;\n\n        CBox bounds;\n        if (PSTATE->exclusive == -1)\n            bounds = full_area;\n        else\n            bounds = *usableArea;\n\n        const Vector2D OLDSIZE = {ls->m_geometry.width, ls->m_geometry.height};\n\n        CBox           box = {{}, PSTATE->desiredSize};\n        // Horizontal axis\n        const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;\n        if (box.width == 0)\n            box.x = bounds.x;\n        else if ((PSTATE->anchor & both_horiz) == both_horiz)\n            box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT))\n            box.x = bounds.x;\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT))\n            box.x = bounds.x + (bounds.width - box.width);\n        else\n            box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));\n\n        // Vertical axis\n        const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;\n        if (box.height == 0)\n            box.y = bounds.y;\n        else if ((PSTATE->anchor & both_vert) == both_vert)\n            box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP))\n            box.y = bounds.y;\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM))\n            box.y = bounds.y + (bounds.height - box.height);\n        else\n            box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));\n\n        // Margin\n        if (box.width == 0) {\n            box.x += PSTATE->margin.left;\n            box.width = bounds.width - (PSTATE->margin.left + PSTATE->margin.right);\n        } else if ((PSTATE->anchor & both_horiz) == both_horiz)\n            ; // don't apply margins\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT))\n            box.x += PSTATE->margin.left;\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT))\n            box.x -= PSTATE->margin.right;\n\n        if (box.height == 0) {\n            box.y += PSTATE->margin.top;\n            box.height = bounds.height - (PSTATE->margin.top + PSTATE->margin.bottom);\n        } else if ((PSTATE->anchor & both_vert) == both_vert)\n            ; // don't apply margins\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP))\n            box.y += PSTATE->margin.top;\n        else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM))\n            box.y -= PSTATE->margin.bottom;\n\n        if (box.width <= 0 || box.height <= 0) {\n            Log::logger->log(Log::ERR, \"LayerSurface {:x} has a negative/zero w/h???\", rc<uintptr_t>(ls.get()));\n            continue;\n        }\n\n        box.round(); // fix rounding errors\n\n        ls->m_geometry = box;\n\n        applyExclusive(*usableArea, PSTATE->anchor, PSTATE->exclusive, PSTATE->exclusiveEdge, PSTATE->margin.top, PSTATE->margin.right, PSTATE->margin.bottom, PSTATE->margin.left);\n\n        if (Vector2D{box.width, box.height} != OLDSIZE)\n            ls->m_layerSurface->configure(box.size());\n\n        *ls->m_realPosition = box.pos();\n        *ls->m_realSize     = box.size();\n    }\n}\n\nvoid IHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) {\n    const auto PMONITOR = g_pCompositor->getMonitorFromID(monitor);\n\n    if (!PMONITOR || PMONITOR->m_size.x <= 0 || PMONITOR->m_size.y <= 0)\n        return;\n\n    // Reset the reserved\n    PMONITOR->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_LS);\n\n    const CBox ORIGINAL_USABLE_AREA = PMONITOR->logicalBoxMinusReserved();\n    CBox       usableArea           = ORIGINAL_USABLE_AREA;\n\n    for (auto& la : PMONITOR->m_layerSurfaceLayers) {\n        std::ranges::stable_sort(\n            la, [](const PHLLSREF& a, const PHLLSREF& b) { return a->m_ruleApplicator->order().valueOrDefault() > b->m_ruleApplicator->order().valueOrDefault(); });\n    }\n\n    for (auto const& la : PMONITOR->m_layerSurfaceLayers)\n        arrangeLayerArray(PMONITOR, la, true, &usableArea);\n\n    for (auto const& la : PMONITOR->m_layerSurfaceLayers)\n        arrangeLayerArray(PMONITOR, la, false, &usableArea);\n\n    PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_LS, Desktop::CReservedArea{ORIGINAL_USABLE_AREA, usableArea});\n\n    // damage the monitor if can\n    damageMonitor(PMONITOR);\n\n    g_layoutManager->invalidateMonitorGeometries(PMONITOR);\n}\n\nvoid IHyprRenderer::damageSurface(SP<CWLSurfaceResource> pSurface, double x, double y, double scale) {\n    if (!pSurface)\n        return; // wut?\n\n    if (g_pCompositor->m_unsafeState)\n        return;\n\n    const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface);\n    if (!WLSURF) {\n        Log::logger->log(Log::ERR, \"BUG THIS: No CWLSurface for surface in damageSurface!!!\");\n        return;\n    }\n\n    // hack: schedule frame events\n    if (!WLSURF->resource()->m_current.callbacks.empty() && pSurface->m_hlSurface) {\n        const auto BOX = pSurface->m_hlSurface->getSurfaceBoxGlobal();\n        if (BOX && !BOX->empty()) {\n            for (auto const& m : g_pCompositor->m_monitors) {\n                if (!m->m_output)\n                    continue;\n\n                if (BOX->overlaps(m->logicalBox()))\n                    g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);\n            }\n        }\n    }\n\n    CRegion damageBox = WLSURF->computeDamage();\n    if (damageBox.empty())\n        return;\n\n    if (scale != 1.0)\n        damageBox.scale(scale);\n\n    damageBox.translate({x, y});\n\n    CRegion damageBoxForEach;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (!m->m_output)\n            continue;\n\n        damageBoxForEach.set(damageBox);\n        damageBoxForEach.translate({-m->m_position.x, -m->m_position.y}).scale(m->m_scale);\n\n        m->addDamage(damageBoxForEach);\n    }\n\n    static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>(\"debug:log_damage\");\n\n    if (*PLOGDAMAGE)\n        Log::logger->log(Log::DEBUG, \"Damage: Surface (extents): xy: {}, {} wh: {}, {}\", damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y1,\n                         damageBox.pixman()->extents.x2 - damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y2 - damageBox.pixman()->extents.y1);\n}\n\nvoid IHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) {\n    if (g_pCompositor->m_unsafeState)\n        return;\n\n    CBox       windowBox        = pWindow->getFullWindowBoundingBox();\n    const auto PWINDOWWORKSPACE = pWindow->m_workspace;\n    if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !pWindow->m_pinned)\n        windowBox.translate(PWINDOWWORKSPACE->m_renderOffset->value());\n    windowBox.translate(pWindow->m_floatingOffset);\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (forceFull || shouldRenderWindow(pWindow, m)) { // only damage if window is rendered on monitor\n            CBox fixedDamageBox = {windowBox.x - m->m_position.x, windowBox.y - m->m_position.y, windowBox.width, windowBox.height};\n            fixedDamageBox.scale(m->m_scale);\n            m->addDamage(fixedDamageBox);\n        }\n    }\n\n    for (auto const& wd : pWindow->m_windowDecorations)\n        wd->damageEntire();\n\n    static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>(\"debug:log_damage\");\n\n    if (*PLOGDAMAGE)\n        Log::logger->log(Log::DEBUG, \"Damage: Window ({}): xy: {}, {} wh: {}, {}\", pWindow->m_title, windowBox.x, windowBox.y, windowBox.width, windowBox.height);\n}\n\nvoid IHyprRenderer::damageMonitor(PHLMONITOR pMonitor) {\n    if (g_pCompositor->m_unsafeState || pMonitor->isMirror())\n        return;\n\n    CBox damageBox = {0, 0, INT16_MAX, INT16_MAX};\n    pMonitor->addDamage(damageBox);\n\n    static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>(\"debug:log_damage\");\n\n    if (*PLOGDAMAGE)\n        Log::logger->log(Log::DEBUG, \"Damage: Monitor {}\", pMonitor->m_name);\n}\n\nvoid IHyprRenderer::damageBox(const CBox& box, bool skipFrameSchedule) {\n    if (g_pCompositor->m_unsafeState)\n        return;\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (m->isMirror())\n            continue; // don't damage mirrors traditionally\n\n        if (!skipFrameSchedule) {\n            CBox damageBox = box.copy().translate(-m->m_position).scale(m->m_scale).round();\n            m->addDamage(damageBox);\n        }\n    }\n\n    static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>(\"debug:log_damage\");\n\n    if (*PLOGDAMAGE)\n        Log::logger->log(Log::DEBUG, \"Damage: Box: xy: {}, {} wh: {}, {}\", box.x, box.y, box.w, box.h);\n}\n\nvoid IHyprRenderer::damageBox(const int& x, const int& y, const int& w, const int& h) {\n    CBox box = {x, y, w, h};\n    damageBox(box);\n}\n\nvoid IHyprRenderer::damageRegion(const CRegion& rg) {\n    rg.forEachRect([this](const auto& RECT) { damageBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1); });\n}\n\nvoid IHyprRenderer::damageMirrorsWith(PHLMONITOR pMonitor, const CRegion& pRegion) {\n    for (auto const& mirror : pMonitor->m_mirrors) {\n\n        // transform the damage here, so it won't get clipped by the monitor damage ring\n        auto    monitor = mirror;\n\n        CRegion transformed{pRegion};\n\n        // we want to transform to the same box as in CHyprOpenGLImpl::renderMirrored\n        double scale  = std::min(monitor->m_transformedSize.x / pMonitor->m_transformedSize.x, monitor->m_transformedSize.y / pMonitor->m_transformedSize.y);\n        CBox   monbox = {0, 0, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale};\n        monbox.x      = (monitor->m_transformedSize.x - monbox.w) / 2;\n        monbox.y      = (monitor->m_transformedSize.y - monbox.h) / 2;\n\n        transformed.scale(scale);\n        transformed.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize.x * scale, pMonitor->m_pixelSize.y * scale);\n        transformed.translate(Vector2D(monbox.x, monbox.y));\n\n        mirror->addDamage(transformed);\n\n        g_pCompositor->scheduleFrameForMonitor(mirror.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);\n    }\n}\n\nvoid IHyprRenderer::renderDragIcon(PHLMONITOR pMonitor, const Time::steady_tp& time) {\n    PROTO::data->renderDND(pMonitor, time);\n}\n\nvoid IHyprRenderer::setCursorSurface(SP<Desktop::View::CWLSurface> surf, int hotspotX, int hotspotY, bool force) {\n    m_cursorHasSurface = surf && surf->resource();\n\n    m_lastCursorData.name     = \"\";\n    m_lastCursorData.surf     = surf;\n    m_lastCursorData.hotspotX = hotspotX;\n    m_lastCursorData.hotspotY = hotspotY;\n\n    if (m_cursorHidden && !force)\n        return;\n\n    g_pCursorManager->setCursorSurface(surf, {hotspotX, hotspotY});\n}\n\nvoid IHyprRenderer::setCursorFromName(const std::string& name, bool force) {\n    m_cursorHasSurface = true;\n\n    if (name == m_lastCursorData.name && !force)\n        return;\n\n    m_lastCursorData.name = name;\n\n    static auto getShapeOrDefault = [](std::string_view name) -> wpCursorShapeDeviceV1Shape {\n        const auto it = std::ranges::find(CURSOR_SHAPE_NAMES, name);\n\n        if (it == CURSOR_SHAPE_NAMES.end()) {\n            // clang-format off\n            static const auto overrites = std::unordered_map<std::string_view, wpCursorShapeDeviceV1Shape> {\n              {\"top_side\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE},\n              {\"bottom_side\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE},\n              {\"left_side\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE},\n              {\"right_side\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE},\n              {\"top_left_corner\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE},\n              {\"bottom_left_corner\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE},\n              {\"top_right_corner\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE},\n              {\"bottom_right_corner\",  WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE},\n            };\n            // clang-format on\n\n            if (overrites.contains(name))\n                return overrites.at(name);\n\n            return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;\n        }\n\n        return sc<wpCursorShapeDeviceV1Shape>(std::distance(CURSOR_SHAPE_NAMES.begin(), it));\n    };\n\n    const auto newShape = getShapeOrDefault(name);\n\n    if (newShape != m_lastCursorData.shape) {\n        m_lastCursorData.shapePrevious = m_lastCursorData.shape;\n        m_lastCursorData.switchedTimer.reset();\n    }\n\n    m_lastCursorData.shape = newShape;\n\n    m_lastCursorData.surf.reset();\n\n    if (m_cursorHidden && !force)\n        return;\n\n    g_pCursorManager->setCursorFromName(name);\n}\n\nvoid IHyprRenderer::ensureCursorRenderingMode() {\n    static auto PINVISIBLE     = CConfigValue<Hyprlang::INT>(\"cursor:invisible\");\n    static auto PCURSORTIMEOUT = CConfigValue<Hyprlang::FLOAT>(\"cursor:inactive_timeout\");\n    static auto PHIDEONTOUCH   = CConfigValue<Hyprlang::INT>(\"cursor:hide_on_touch\");\n    static auto PHIDEONTABLET  = CConfigValue<Hyprlang::INT>(\"cursor:hide_on_tablet\");\n    static auto PHIDEONKEY     = CConfigValue<Hyprlang::INT>(\"cursor:hide_on_key_press\");\n\n    if (*PCURSORTIMEOUT <= 0)\n        m_cursorHiddenConditions.hiddenOnTimeout = false;\n    if (*PHIDEONTOUCH == 0)\n        m_cursorHiddenConditions.hiddenOnTouch = false;\n    if (*PHIDEONTABLET == 0)\n        m_cursorHiddenConditions.hiddenOnTablet = false;\n    if (*PHIDEONKEY == 0)\n        m_cursorHiddenConditions.hiddenOnKeyboard = false;\n\n    if (*PCURSORTIMEOUT > 0)\n        m_cursorHiddenConditions.hiddenOnTimeout = *PCURSORTIMEOUT < g_pInputManager->m_lastCursorMovement.getSeconds();\n\n    m_cursorHiddenByCondition =\n        m_cursorHiddenConditions.hiddenOnTimeout || m_cursorHiddenConditions.hiddenOnTouch || m_cursorHiddenConditions.hiddenOnTablet || m_cursorHiddenConditions.hiddenOnKeyboard;\n\n    const bool HIDE = m_cursorHiddenByCondition || (*PINVISIBLE != 0);\n\n    if (HIDE == m_cursorHidden)\n        return;\n\n    if (HIDE)\n        Log::logger->log(Log::DEBUG, \"Hiding the cursor (hl-mandated)\");\n    else\n        Log::logger->log(Log::DEBUG, \"Showing the cursor (hl-mandated)\");\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (!g_pPointerManager->softwareLockedFor(m))\n            continue;\n\n        g_pPointerManager->damageCursor(m, m->shouldSkipScheduleFrameOnMouseEvent());\n    }\n\n    setCursorHidden(HIDE);\n}\n\nvoid IHyprRenderer::setCursorHidden(bool hide) {\n\n    if (hide == m_cursorHidden)\n        return;\n\n    m_cursorHidden = hide;\n\n    if (hide) {\n        g_pPointerManager->resetCursorImage();\n        return;\n    }\n\n    if (m_lastCursorData.surf.has_value())\n        setCursorSurface(m_lastCursorData.surf.value(), m_lastCursorData.hotspotX, m_lastCursorData.hotspotY, true);\n    else if (!m_lastCursorData.name.empty())\n        setCursorFromName(m_lastCursorData.name, true);\n    else\n        setCursorFromName(\"left_ptr\", true);\n}\n\nbool IHyprRenderer::shouldRenderCursor() {\n    return !m_cursorHidden && m_cursorHasSurface;\n}\n\nstd::tuple<float, float, float> IHyprRenderer::getRenderTimes(PHLMONITOR pMonitor) {\n    const auto POVERLAY = &g_pDebugOverlay->m_monitorOverlays[pMonitor];\n\n    float      avgRenderTime = 0;\n    float      maxRenderTime = 0;\n    float      minRenderTime = 9999;\n    for (auto const& rt : POVERLAY->m_lastRenderTimes) {\n        maxRenderTime = std::max(rt, maxRenderTime);\n        minRenderTime = std::min(rt, minRenderTime);\n        avgRenderTime += rt;\n    }\n    avgRenderTime /= POVERLAY->m_lastRenderTimes.empty() ? 1 : POVERLAY->m_lastRenderTimes.size();\n\n    return std::make_tuple<>(avgRenderTime, maxRenderTime, minRenderTime);\n}\n\nstatic int handleCrashLoop(void* data) {\n\n    g_pHyprNotificationOverlay->addNotification(\"Hyprland will crash in \" + std::to_string(10 - sc<int>(g_pHyprRenderer->m_crashingDistort * 2.f)) + \"s.\", CHyprColor(0), 5000,\n                                                ICON_INFO);\n\n    g_pHyprRenderer->m_crashingDistort += 0.5f;\n\n    if (g_pHyprRenderer->m_crashingDistort >= 5.5f)\n        raise(SIGABRT);\n\n    wl_event_source_timer_update(g_pHyprRenderer->m_crashingLoop, 1000);\n\n    return 1;\n}\n\nvoid IHyprRenderer::initiateManualCrash() {\n    g_pHyprNotificationOverlay->addNotification(\"Manual crash initiated. Farewell...\", CHyprColor(0), 5000, ICON_INFO);\n\n    m_crashingLoop = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, handleCrashLoop, nullptr);\n    wl_event_source_timer_update(m_crashingLoop, 1000);\n\n    m_crashingInProgress = true;\n    m_crashingDistort    = 0.5;\n\n    m_globalTimer.reset();\n\n    static auto PDT = rc<Hyprlang::INT* const*>(g_pConfigManager->getConfigValuePtr(\"debug:damage_tracking\"));\n\n    **PDT = 0;\n}\n\nconst SRenderData& IHyprRenderer::renderData() {\n    return m_renderData;\n}\n\nSP<IRenderbuffer> IHyprRenderer::getOrCreateRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {\n    auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; });\n\n    if (it != m_renderbuffers.end())\n        return *it;\n\n    auto buf = getOrCreateRenderbufferInternal(buffer, fmt);\n\n    if (!buf->good())\n        return nullptr;\n\n    m_renderbuffers.emplace_back(buf);\n    return buf;\n}\n\nbool IHyprRenderer::beginFullFakeRender(PHLMONITOR pMonitor, CRegion& damage, SP<IFramebuffer> fb) {\n    return beginRender(pMonitor, damage, RENDER_MODE_FULL_FAKE, nullptr, fb, true);\n}\n\nbool IHyprRenderer::beginRenderToBuffer(PHLMONITOR pMonitor, CRegion& damage, SP<IHLBuffer> buffer, bool simple) {\n    return beginRender(pMonitor, damage, RENDER_MODE_TO_BUFFER, buffer, nullptr, simple);\n}\n\nvoid IHyprRenderer::onRenderbufferDestroy(IRenderbuffer* rb) {\n    std::erase_if(m_renderbuffers, [&](const auto& rbo) { return rbo.get() == rb; });\n}\n\nbool IHyprRenderer::isNvidia() {\n    return m_nvidia;\n}\n\nbool IHyprRenderer::isIntel() {\n    return m_intel;\n}\n\nbool IHyprRenderer::isSoftware() {\n    return m_software;\n}\n\nbool IHyprRenderer::isMgpu() {\n    return m_mgpu;\n}\n\nvoid IHyprRenderer::addWindowToRenderUnfocused(PHLWINDOW window) {\n    static auto PFPS = CConfigValue<Hyprlang::INT>(\"misc:render_unfocused_fps\");\n\n    if (std::ranges::find(m_renderUnfocused, window) != m_renderUnfocused.end())\n        return;\n\n    m_renderUnfocused.emplace_back(window);\n\n    if (!m_renderUnfocusedTimer->armed())\n        m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS));\n}\n\nvoid IHyprRenderer::makeSnapshot(PHLWINDOW pWindow) {\n    // we trust the window is valid.\n    const auto PMONITOR = pWindow->m_monitor.lock();\n\n    if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0)\n        return;\n\n    if (!shouldRenderWindow(pWindow))\n        return; // ignore, window is not being rendered\n\n    Log::logger->log(Log::DEBUG, \"renderer: making a snapshot of {:x}\", rc<uintptr_t>(pWindow.get()));\n\n    // we need to \"damage\" the entire monitor\n    // so that we render the entire window\n    // this is temporary, doesn't mess with the actual damage\n    CRegion      fakeDamage{0, 0, sc<int>(PMONITOR->m_transformedSize.x), sc<int>(PMONITOR->m_transformedSize.y)};\n\n    PHLWINDOWREF ref{pWindow};\n\n    if (!ref->m_snapshotFB)\n        ref->m_snapshotFB = createFB(\"window snapshot\");\n\n    const auto PFRAMEBUFFER = ref->m_snapshotFB;\n\n    PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888);\n\n    beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER);\n\n    m_bRenderingSnapshot = true;\n\n    draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}), {});\n    startRenderPass();\n\n    Log::logger->log(Log::DEBUG, \"renderer: cleared a snapshot of {:x}\", rc<uintptr_t>(pWindow.get()));\n\n    renderWindow(pWindow, PMONITOR, Time::steadyNow(), !pWindow->m_X11DoesntWantBorders, RENDER_PASS_ALL);\n\n    Log::logger->log(Log::DEBUG, \"renderer: rendered a snapshot of {:x}\", rc<uintptr_t>(pWindow.get()));\n\n    endRender();\n\n    Log::logger->log(Log::DEBUG, \"renderer: made a snapshot of {:x}\", rc<uintptr_t>(pWindow.get()));\n\n    m_bRenderingSnapshot = false;\n}\n\nvoid IHyprRenderer::makeSnapshot(PHLLS pLayer) {\n    // we trust the window is valid.\n    const auto PMONITOR = pLayer->m_monitor.lock();\n\n    if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"renderer: making a snapshot of layer {:x}\", rc<uintptr_t>(pLayer.get()));\n\n    // we need to \"damage\" the entire monitor\n    // so that we render the entire window\n    // this is temporary, doesn't mess with the actual damage\n    CRegion fakeDamage{0, 0, sc<int>(PMONITOR->m_transformedSize.x), sc<int>(PMONITOR->m_transformedSize.y)};\n\n    if (!pLayer->m_snapshotFB)\n        pLayer->m_snapshotFB = createFB(\"layer snapshot\");\n\n    const auto PFRAMEBUFFER = pLayer->m_snapshotFB;\n\n    PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888);\n\n    beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER);\n\n    m_bRenderingSnapshot = true;\n\n    draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}), {});\n    startRenderPass();\n\n    Log::logger->log(Log::DEBUG, \"renderer: cleared a snapshot of layer {:x}\", rc<uintptr_t>(pLayer.get()));\n\n    // draw the layer\n    renderLayer(pLayer, PMONITOR, Time::steadyNow());\n\n    Log::logger->log(Log::DEBUG, \"renderer: rendered a snapshot of layer {:x}\", rc<uintptr_t>(pLayer.get()));\n\n    endRender();\n\n    Log::logger->log(Log::DEBUG, \"renderer: made a snapshot of layer {:x}\", rc<uintptr_t>(pLayer.get()));\n\n    m_bRenderingSnapshot = false;\n}\n\nvoid IHyprRenderer::makeSnapshot(WP<Desktop::View::CPopup> popup) {\n    // we trust the window is valid.\n    const auto PMONITOR = popup->getMonitor();\n\n    if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0)\n        return;\n\n    if (!popup->aliveAndVisible())\n        return;\n\n    Log::logger->log(Log::DEBUG, \"renderer: making a snapshot of {:x}\", rc<uintptr_t>(popup.get()));\n\n    CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};\n\n    if (!popup->m_snapshotFB)\n        popup->m_snapshotFB = createFB(\"popup shapshot\");\n\n    const auto PFRAMEBUFFER = popup->m_snapshotFB;\n\n    PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888);\n\n    beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER);\n\n    m_bRenderingSnapshot = true;\n\n    draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}), {});\n\n    CSurfacePassElement::SRenderData renderdata;\n    renderdata.pos             = popup->coordsGlobal();\n    renderdata.alpha           = 1.F;\n    renderdata.dontRound       = true; // don't round popups\n    renderdata.pMonitor        = PMONITOR;\n    renderdata.squishOversized = false; // don't squish popups\n    renderdata.popup           = true;\n    renderdata.blur            = false;\n\n    popup->wlSurface()->resource()->breadthfirst(\n        [this, &renderdata](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {\n            if (!s->m_current.texture)\n                return;\n\n            if (s->m_current.size.x < 1 || s->m_current.size.y < 1)\n                return;\n\n            renderdata.localPos    = offset;\n            renderdata.texture     = s->m_current.texture;\n            renderdata.surface     = s;\n            renderdata.mainSurface = false;\n            m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));\n            renderdata.surfaceCounter++;\n        },\n        nullptr);\n\n    endRender();\n\n    m_bRenderingSnapshot = false;\n}\n\nvoid IHyprRenderer::renderSnapshot(PHLWINDOW pWindow) {\n    static auto  PDIMAROUND = CConfigValue<Hyprlang::FLOAT>(\"decoration:dim_around\");\n\n    PHLWINDOWREF ref{pWindow};\n\n    if (!ref->m_snapshotFB)\n        return;\n\n    const auto FBDATA = ref->m_snapshotFB;\n\n    if (!FBDATA->getTexture())\n        return;\n\n    const auto PMONITOR = pWindow->m_monitor.lock();\n\n    CBox       windowBox;\n    // some mafs to figure out the correct box\n    // the originalClosedPos is relative to the monitor's pos\n    Vector2D scaleXY = Vector2D((PMONITOR->m_scale * pWindow->m_realSize->value().x / (pWindow->m_originalClosedSize.x * PMONITOR->m_scale)),\n                                (PMONITOR->m_scale * pWindow->m_realSize->value().y / (pWindow->m_originalClosedSize.y * PMONITOR->m_scale)));\n\n    windowBox.width  = PMONITOR->m_transformedSize.x * scaleXY.x;\n    windowBox.height = PMONITOR->m_transformedSize.y * scaleXY.y;\n    windowBox.x      = ((pWindow->m_realPosition->value().x - PMONITOR->m_position.x) * PMONITOR->m_scale) - ((pWindow->m_originalClosedPos.x * PMONITOR->m_scale) * scaleXY.x);\n    windowBox.y      = ((pWindow->m_realPosition->value().y - PMONITOR->m_position.y) * PMONITOR->m_scale) - ((pWindow->m_originalClosedPos.y * PMONITOR->m_scale) * scaleXY.y);\n\n    CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};\n\n    if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault()) {\n        CRectPassElement::SRectData data;\n\n        data.box   = {0, 0, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y};\n        data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pWindow->m_alpha->value());\n\n        m_renderPass.add(makeUnique<CRectPassElement>(data));\n    }\n\n    if (shouldBlur(pWindow)) {\n        CRectPassElement::SRectData data;\n        data.box           = CBox{pWindow->m_realPosition->value(), pWindow->m_realSize->value()}.translate(-PMONITOR->m_position).scale(PMONITOR->m_scale).round();\n        data.color         = CHyprColor{0, 0, 0, 0};\n        data.blur          = true;\n        data.blurA         = sqrt(pWindow->m_alpha->value()); // sqrt makes the blur fadeout more realistic.\n        data.round         = pWindow->rounding();\n        data.roundingPower = pWindow->roundingPower();\n        data.xray          = pWindow->m_ruleApplicator->xray().valueOr(false);\n\n        m_renderPass.add(makeUnique<CRectPassElement>(data));\n    }\n\n    CTexPassElement::SRenderData data;\n    data.flipEndFrame = true;\n    data.tex          = FBDATA->getTexture();\n    data.box          = windowBox;\n    data.a            = pWindow->m_alpha->value();\n    data.damage       = fakeDamage;\n\n    m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n\nvoid IHyprRenderer::renderSnapshot(PHLLS pLayer) {\n    if (!pLayer->m_snapshotFB)\n        return;\n\n    const auto FBDATA = pLayer->m_snapshotFB;\n\n    if (!FBDATA->getTexture())\n        return;\n\n    const auto PMONITOR = pLayer->m_monitor.lock();\n\n    CBox       layerBox;\n    // some mafs to figure out the correct box\n    // the originalClosedPos is relative to the monitor's pos\n    Vector2D scaleXY = Vector2D((PMONITOR->m_scale * pLayer->m_realSize->value().x / (pLayer->m_geometry.w * PMONITOR->m_scale)),\n                                (PMONITOR->m_scale * pLayer->m_realSize->value().y / (pLayer->m_geometry.h * PMONITOR->m_scale)));\n\n    layerBox.width  = PMONITOR->m_transformedSize.x * scaleXY.x;\n    layerBox.height = PMONITOR->m_transformedSize.y * scaleXY.y;\n    layerBox.x =\n        ((pLayer->m_realPosition->value().x - PMONITOR->m_position.x) * PMONITOR->m_scale) - (((pLayer->m_geometry.x - PMONITOR->m_position.x) * PMONITOR->m_scale) * scaleXY.x);\n    layerBox.y =\n        ((pLayer->m_realPosition->value().y - PMONITOR->m_position.y) * PMONITOR->m_scale) - (((pLayer->m_geometry.y - PMONITOR->m_position.y) * PMONITOR->m_scale) * scaleXY.y);\n\n    CRegion                      fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};\n\n    const bool                   SHOULD_BLUR = shouldBlur(pLayer);\n\n    CTexPassElement::SRenderData data;\n    data.flipEndFrame = true;\n    data.tex          = FBDATA->getTexture();\n    data.box          = layerBox;\n    data.a            = pLayer->m_alpha->value();\n    data.damage       = fakeDamage;\n    data.blur         = SHOULD_BLUR;\n    data.blurA        = sqrt(pLayer->m_alpha->value()); // sqrt makes the blur fadeout more realistic.\n    if (SHOULD_BLUR)\n        data.ignoreAlpha = pLayer->m_ruleApplicator->ignoreAlpha().valueOr(0.01F) /* ignore the alpha 0 regions */;\n\n    m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n\nvoid IHyprRenderer::renderSnapshot(WP<Desktop::View::CPopup> popup) {\n    if (!popup->m_snapshotFB)\n        return;\n\n    static CConfigValue PBLURIGNOREA = CConfigValue<Hyprlang::FLOAT>(\"decoration:blur:popups_ignorealpha\");\n\n    const auto          FBDATA = popup->m_snapshotFB;\n\n    if (!FBDATA->getTexture())\n        return;\n\n    const auto PMONITOR = popup->getMonitor();\n\n    if (!PMONITOR)\n        return;\n\n    CRegion                      fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};\n\n    const bool                   SHOULD_BLUR = shouldBlur(popup);\n\n    CTexPassElement::SRenderData data;\n    data.flipEndFrame          = true;\n    data.tex                   = FBDATA->getTexture();\n    data.box                   = {{}, PMONITOR->m_transformedSize};\n    data.a                     = popup->m_alpha->value();\n    data.damage                = fakeDamage;\n    data.blur                  = SHOULD_BLUR;\n    data.blurA                 = sqrt(popup->m_alpha->value()); // sqrt makes the blur fadeout more realistic.\n    data.blockBlurOptimization = SHOULD_BLUR;                   // force no xray on this (popups never have xray)\n    if (SHOULD_BLUR)\n        data.ignoreAlpha = std::max(*PBLURIGNOREA, 0.01F); /* ignore the alpha 0 regions */\n\n    m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n}\n\nNColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription() {\n    // TODO\n    // const bool  IS_MONITOR_ICC  = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present;\n    // const auto  sdrEOTF         = NTransferFunction::fromConfig(IS_MONITOR_ICC);\n    // const auto  CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB;\n\n    return m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF});\n}\n\nbool IHyprRenderer::shouldBlur(PHLLS ls) {\n    if (m_bRenderingSnapshot)\n        return false;\n\n    static auto PBLUR = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n    return *PBLUR && ls->m_ruleApplicator->blur().valueOrDefault();\n}\n\nbool IHyprRenderer::shouldBlur(PHLWINDOW w) {\n    if (m_bRenderingSnapshot)\n        return false;\n\n    static auto PBLUR     = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n    const bool  DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->opaque();\n    return *PBLUR && !DONT_BLUR;\n}\n\nbool IHyprRenderer::shouldBlur(WP<Desktop::View::CPopup> p) {\n    static CConfigValue PBLURPOPUPS = CConfigValue<Hyprlang::INT>(\"decoration:blur:popups\");\n    static CConfigValue PBLUR       = CConfigValue<Hyprlang::INT>(\"decoration:blur:enabled\");\n\n    return *PBLURPOPUPS && *PBLUR;\n}\n\nSP<ITexture> IHyprRenderer::renderSplash(const std::function<SP<ITexture>(const int, const int, unsigned char* const)>& handleData, const int fontSize, const int maxWidth,\n                                         const int maxHeight) {\n    static auto PSPLASHCOLOR = CConfigValue<Hyprlang::INT>(\"misc:col.splash\");\n    static auto PSPLASHFONT  = CConfigValue<std::string>(\"misc:splash_font_family\");\n    static auto FALLBACKFONT = CConfigValue<std::string>(\"misc:font_family\");\n\n    const auto  FONTFAMILY = *PSPLASHFONT != STRVAL_EMPTY ? *PSPLASHFONT : *FALLBACKFONT;\n    const auto  COLOR      = CHyprColor(*PSPLASHCOLOR);\n\n    const auto  CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, maxWidth, maxHeight);\n    const auto  CAIRO        = cairo_create(CAIROSURFACE);\n\n    cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_GOOD);\n    cairo_save(CAIRO);\n    cairo_set_source_rgba(CAIRO, 0, 0, 0, 0);\n    cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE);\n    cairo_paint(CAIRO);\n    cairo_restore(CAIRO);\n\n    PangoLayout*          layoutText = pango_cairo_create_layout(CAIRO);\n    PangoFontDescription* pangoFD    = pango_font_description_new();\n\n    pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str());\n    pango_font_description_set_absolute_size(pangoFD, fontSize * PANGO_SCALE);\n    pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL);\n    pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL);\n    pango_layout_set_font_description(layoutText, pangoFD);\n\n    cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a);\n    int textW = 0, textH = 0;\n    pango_layout_set_text(layoutText, g_pCompositor->m_currentSplash.c_str(), -1);\n    pango_layout_get_size(layoutText, &textW, &textH);\n    textW = std::ceil((float)textW / PANGO_SCALE + fontSize / 10.f);\n    textH = std::ceil((float)textH / PANGO_SCALE + fontSize / 10.f);\n\n    cairo_move_to(CAIRO, 0, 0);\n    pango_cairo_show_layout(CAIRO, layoutText);\n\n    pango_font_description_free(pangoFD);\n    g_object_unref(layoutText);\n\n    cairo_surface_flush(CAIROSURFACE);\n\n    const auto smallSurf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH);\n    const auto small     = cairo_create(smallSurf);\n    cairo_set_source_surface(small, CAIROSURFACE, 0, 0);\n    cairo_rectangle(small, 0, 0, textW, textH);\n    cairo_set_operator(small, CAIRO_OPERATOR_SOURCE);\n    cairo_fill(small);\n    cairo_surface_flush(smallSurf);\n\n    auto tex = handleData(textW, textH, cairo_image_surface_get_data(smallSurf));\n\n    cairo_surface_destroy(smallSurf);\n    cairo_destroy(small);\n\n    cairo_surface_destroy(CAIROSURFACE);\n    cairo_destroy(CAIRO);\n    return tex;\n}\n\nbool IHyprRenderer::needsACopyFB(PHLMONITOR mon) {\n    return !mon->m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(mon);\n}\n"
  },
  {
    "path": "src/render/Renderer.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include <hyprgraphics/color/Color.hpp>\n#include <hyprutils/math/Box.hpp>\n#include <list>\n#include <optional>\n#include \"../helpers/Monitor.hpp\"\n#include \"../desktop/view/LayerSurface.hpp\"\n#include \"./pass/Pass.hpp\"\n#include \"Renderbuffer.hpp\"\n#include \"../helpers/time/Timer.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include \"../helpers/time/Time.hpp\"\n#include \"../../protocols/cursor-shape-v1.hpp\"\n#include \"../desktop/view/Popup.hpp\"\n#include \"Framebuffer.hpp\"\n#include \"Texture.hpp\"\n#include \"pass/BorderPassElement.hpp\"\n#include \"pass/ClearPassElement.hpp\"\n#include \"pass/FramebufferElement.hpp\"\n#include \"pass/PreBlurElement.hpp\"\n#include \"pass/RectPassElement.hpp\"\n#include \"pass/RendererHintsPassElement.hpp\"\n#include \"pass/ShadowPassElement.hpp\"\n#include \"pass/SurfacePassElement.hpp\"\n#include \"pass/TexPassElement.hpp\"\n#include \"pass/TextureMatteElement.hpp\"\n\nstruct SMonitorRule;\nclass CWorkspace;\nclass CInputPopup;\nclass IHLBuffer;\nclass CEventLoopTimer;\nclass CRenderPass;\n\nconst std::vector<const char*> ASSET_PATHS = {\n#ifdef DATAROOTDIR\n    DATAROOTDIR,\n#endif\n    \"/usr/share\",\n    \"/usr/local/share\",\n};\nclass CToplevelExportProtocolManager;\nclass CInputManager;\nstruct SSessionLockSurface;\nnamespace Screenshare {\n    class CScreenshareFrame;\n};\n\nenum eDamageTrackingModes : int8_t {\n    DAMAGE_TRACKING_INVALID = -1,\n    DAMAGE_TRACKING_NONE    = 0,\n    DAMAGE_TRACKING_MONITOR,\n    DAMAGE_TRACKING_FULL,\n};\n\nenum eRenderPassMode : uint8_t {\n    RENDER_PASS_ALL = 0,\n    RENDER_PASS_MAIN,\n    RENDER_PASS_POPUP\n};\n\nenum eRenderMode : uint8_t {\n    RENDER_MODE_NORMAL              = 0,\n    RENDER_MODE_FULL_FAKE           = 1,\n    RENDER_MODE_TO_BUFFER           = 2,\n    RENDER_MODE_TO_BUFFER_READ_ONLY = 3,\n};\n\nstruct SRenderWorkspaceUntilData {\n    PHLLS     ls;\n    PHLWINDOW w;\n};\n\nenum eRenderProjectionType : uint8_t {\n    RPT_MONITOR,\n    RPT_MIRROR,\n    RPT_FB,\n    RPT_EXPORT,\n};\n\nstruct SRenderData {\n    // can be private\n    Mat3x3 targetProjection;\n\n    // ----------------------\n\n    // used by public\n    Vector2D              fbSize = {-1, -1};\n    PHLMONITORREF         pMonitor;\n\n    eRenderProjectionType projectionType = RPT_MONITOR;\n\n    SP<IFramebuffer>      currentFB = nullptr; // current rendering to\n    SP<IFramebuffer>      mainFB    = nullptr; // main to render to\n    SP<IFramebuffer>      outFB     = nullptr; // out to render to (if offloaded, etc)\n\n    CRegion               damage;\n    CRegion               finalDamage; // damage used for funal off -> main\n\n    SRenderModifData      renderModif;\n    float                 mouseZoomFactor    = 1.f;\n    bool                  mouseZoomUseMouse  = true; // true by default\n    bool                  useNearestNeighbor = false;\n    bool                  blockScreenShader  = false;\n\n    Vector2D              primarySurfaceUVTopLeft     = Vector2D(-1, -1);\n    Vector2D              primarySurfaceUVBottomRight = Vector2D(-1, -1);\n\n    // TODO remove and pass directly\n    CBox                   clipBox = {}; // scaled coordinates\n    PHLWINDOWREF           currentWindow;\n    WP<CWLSurfaceResource> surface;\n\n    bool                   transformDamage = true;\n    bool                   noSimplify      = false;\n};\n\nstruct STFRange {\n    float min = 0;\n    float max = 80;\n};\n\nstruct SCMSettings {\n    NColorManagement::eTransferFunction  sourceTF = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;\n    NColorManagement::eTransferFunction  targetTF = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;\n    STFRange                             srcTFRange;\n    STFRange                             dstTFRange;\n    float                                srcRefLuminance = 80;\n    float                                dstRefLuminance = 80;\n    std::array<std::array<double, 3>, 3> convertMatrix;\n\n    bool                                 needsTonemap    = false;\n    float                                maxLuminance    = 80;\n    float                                dstMaxLuminance = 80;\n    std::array<std::array<double, 3>, 3> dstPrimaries2XYZ;\n    bool                                 needsSDRmod             = false;\n    float                                sdrSaturation           = 1.0;\n    float                                sdrBrightnessMultiplier = 1.0;\n};\n\nclass IHyprRenderer {\n  public:\n    IHyprRenderer();\n    virtual ~IHyprRenderer();\n\n    WP<CHyprOpenGLImpl> glBackend();\n\n    void                renderMonitor(PHLMONITOR pMonitor, bool commit = true);\n    void                arrangeLayersForMonitor(const MONITORID&);\n    void                damageSurface(SP<CWLSurfaceResource>, double, double, double scale = 1.0);\n    void                damageWindow(PHLWINDOW, bool forceFull = false);\n    void                damageBox(const CBox&, bool skipFrameSchedule = false);\n    void                damageBox(const int& x, const int& y, const int& w, const int& h);\n    void                damageRegion(const CRegion&);\n    void                damageMonitor(PHLMONITOR);\n    void                damageMirrorsWith(PHLMONITOR, const CRegion&);\n    bool                shouldRenderWindow(PHLWINDOW, PHLMONITOR);\n    bool                shouldRenderWindow(PHLWINDOW);\n    void                ensureCursorRenderingMode();\n    bool                shouldRenderCursor();\n    void                setCursorHidden(bool hide);\n    void calculateUVForSurface(PHLWINDOW, SP<CWLSurfaceResource>, PHLMONITOR pMonitor, bool main = false, const Vector2D& projSize = {}, const Vector2D& projSizeUnscaled = {},\n                               bool fixMisalignedFSV1 = false);\n    std::tuple<float, float, float>     getRenderTimes(PHLMONITOR pMonitor); // avg max min\n    void                                ensureLockTexturesRendered(bool load);\n    void                                renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry);\n    void                                setCursorSurface(SP<Desktop::View::CWLSurface> surf, int hotspotX, int hotspotY, bool force = false);\n    void                                setCursorFromName(const std::string& name, bool force = false);\n    void                                onRenderbufferDestroy(IRenderbuffer* rb);\n    bool                                isNvidia();\n    bool                                isIntel();\n    bool                                isSoftware();\n    bool                                isMgpu();\n    void                                addWindowToRenderUnfocused(PHLWINDOW window);\n    void                                makeSnapshot(PHLWINDOW);\n    void                                makeSnapshot(PHLLS);\n    void                                makeSnapshot(WP<Desktop::View::CPopup>);\n    void                                renderSnapshot(PHLWINDOW);\n    void                                renderSnapshot(PHLLS);\n    void                                renderSnapshot(WP<Desktop::View::CPopup>);\n    bool                                beginFullFakeRender(PHLMONITOR pMonitor, CRegion& damage, SP<IFramebuffer> fb);\n    bool                                beginRenderToBuffer(PHLMONITOR pMonitor, CRegion& damage, SP<IHLBuffer> buffer, bool simple = false);\n    virtual void                        startRenderPass() {};\n    virtual void                        endRender(const std::function<void()>& renderingDoneCallback = {}) = 0;\n\n    NColorManagement::PImageDescription workBufferImageDescription();\n    bool                                m_bBlockSurfaceFeedback = false;\n    bool                                m_bRenderingSnapshot    = false;\n    PHLMONITORREF                       m_mostHzMonitor;\n    bool                                m_directScanoutBlocked = false;\n\n    void                                setSurfaceScanoutMode(SP<CWLSurfaceResource> surface, PHLMONITOR monitor); // nullptr monitor resets\n\n    void                                initiateManualCrash();\n    const SRenderData&                  renderData();\n\n    bool                                m_crashingInProgress = false;\n    float                               m_crashingDistort    = 0.5f;\n    wl_event_source*                    m_crashingLoop       = nullptr;\n    wl_event_source*                    m_cursorTicker       = nullptr;\n\n    std::vector<CHLBufferReference>     m_usedAsyncBuffers;\n\n    struct {\n        int                                          hotspotX      = 0;\n        int                                          hotspotY      = 0;\n        wpCursorShapeDeviceV1Shape                   shape         = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;\n        wpCursorShapeDeviceV1Shape                   shapePrevious = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;\n        CTimer                                       switchedTimer;\n        std::optional<SP<Desktop::View::CWLSurface>> surf;\n        std::string                                  name;\n    } m_lastCursorData;\n\n    CRenderPass               m_renderPass;\n\n    SP<ITexture>              renderSplash(const std::function<SP<ITexture>(const int, const int, unsigned char* const)>& handleData, const int fontSize, const int maxWidth = 1024,\n                                           const int maxHeight = 1024);\n\n    virtual SP<IRenderbuffer> getOrCreateRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt); // TODO? move to protected and fix CPointerManager::renderHWCursorBuffer\n    bool                      commitPendingAndDoExplicitSync(PHLMONITOR pMonitor);                   // TODO? move to protected and fix CMonitorFrameScheduler::onPresented\n    SRenderData               m_renderData;                                                          // TODO? move to protected and fix CRenderPass\n    SP<ITexture>              m_screencopyDeniedTexture;                                             // TODO? make readonly\n    uint                      m_failedAssetsNo     = 0;                                              // TODO? make readonly\n    bool                      m_reloadScreenShader = true;                                           // at launch it can be set\n    CTimer                    m_globalTimer;\n\n    void                      draw(WP<IPassElement> element, const CRegion& damage);\n    virtual SP<ITexture>      createStencilTexture(const int width, const int height)                                                                                   = 0;\n    virtual SP<ITexture>      createTexture(bool opaque = false)                                                                                                        = 0;\n    virtual SP<ITexture>      createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false) = 0;\n    virtual SP<ITexture>      createTexture(const Aquamarine::SDMABUFAttrs&, bool opaque = false)                                                                       = 0;\n    virtual SP<ITexture>      createTexture(const int width, const int height, unsigned char* const)                                                                    = 0;\n    virtual SP<ITexture>      createTexture(cairo_surface_t* cairo)                                                                                                     = 0;\n    virtual SP<ITexture>      createTexture(std::span<const float> lut3D, size_t N)                                                                                     = 0;\n    virtual SP<ITexture>      createTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy = false);\n    virtual SP<ITexture> renderText(const std::string& text, CHyprColor col, int pt, bool italic = false, const std::string& fontFamily = \"\", int maxWidth = 0, int weight = 400);\n    SP<ITexture>         loadAsset(const std::string& filename);\n    virtual bool         shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow);\n    virtual bool         explicitSyncSupported()                                                                                      = 0;\n    virtual std::vector<SDRMFormat> getDRMFormats()                                                                                   = 0;\n    virtual std::vector<uint64_t>   getDRMFormatModifiers(DRMFormat format)                                                           = 0;\n    virtual SP<IFramebuffer>        createFB(const std::string& name = \"\")                                                            = 0;\n    virtual void                    disableScissor()                                                                                  = 0;\n    virtual void                    blend(bool enabled)                                                                               = 0;\n    virtual void                    drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) = 0;\n    virtual void                    setViewport(int x, int y, int width, int height)                                                  = 0;\n\n    bool                            preBlurQueued(PHLMONITORREF pMonitor);\n    void                            pushMonitorTransformEnabled(bool enabled);\n    void                            popMonitorTransformEnabled();\n    bool                            monitorTransformEnabled();\n\n    void                            setProjectionType(const Vector2D& fbSize);\n    void                            setProjectionType(eRenderProjectionType projectionType);\n    Mat3x3                          getBoxProjection(const CBox& box, std::optional<eTransform> transform = std::nullopt);\n    Mat3x3                          projectBoxToTarget(const CBox& box, std::optional<eTransform> transform = std::nullopt);\n\n    SP<ITexture>                    blurMainFramebuffer(float a, CRegion* originalDamage);\n    virtual SP<ITexture>            blurFramebuffer(SP<IFramebuffer> source, float a, CRegion* originalDamage) = 0;\n    void                            preBlurForCurrentMonitor(CRegion* fakeDamage);\n\n    SCMSettings                     getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,\n                                                  SP<CWLSurfaceResource> surface = nullptr, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1);\n    virtual bool                    reloadShaders(const std::string& path = \"\") = 0;\n\n    bool                            needsACopyFB(PHLMONITOR mon);\n\n  protected:\n    virtual void              renderOffToMain(IFramebuffer* off)                                            = 0;\n    virtual SP<IRenderbuffer> getOrCreateRenderbufferInternal(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) = 0;\n    void                      renderMirrored();\n    void                      setDamage(const CRegion& damage_, std::optional<CRegion> finalDamage);\n    // if RENDER_MODE_NORMAL, provided damage will be written to.\n    // otherwise, it will be the one used.\n    bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP<IHLBuffer> buffer = {}, SP<IFramebuffer> fb = nullptr, bool simple = false);\n\n    virtual bool beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple = false) {\n        return false;\n    };\n    virtual bool beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP<IFramebuffer> fb, bool simple = false) {\n        return false;\n    };\n    virtual void initRender() {};\n    virtual bool initRenderBuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {\n        return false;\n    };\n\n    SP<ITexture>         getBackground(PHLMONITOR pMonitor);\n    virtual void         draw(CBorderPassElement* element, const CRegion& damage)   = 0;\n    virtual void         draw(CClearPassElement* element, const CRegion& damage)    = 0;\n    virtual void         draw(CFramebufferElement* element, const CRegion& damage)  = 0;\n    virtual void         draw(CPreBlurElement* element, const CRegion& damage)      = 0;\n    virtual void         draw(CRectPassElement* element, const CRegion& damage)     = 0;\n    virtual void         draw(CShadowPassElement* element, const CRegion& damage)   = 0;\n    virtual void         draw(CTexPassElement* element, const CRegion& damage)      = 0;\n    virtual void         draw(CTextureMatteElement* element, const CRegion& damage) = 0;\n    virtual SP<ITexture> getBlurTexture(PHLMONITORREF pMonitor);\n    SP<ITexture>         m_lockDeadTexture;\n    SP<ITexture>         m_lockDead2Texture;\n    SP<ITexture>         m_lockTtyTextTexture;\n    bool                 m_monitorTransformEnabled = false; // do not modify directly\n    std::stack<bool>     m_monitorTransformStack;\n\n    // old private:\n    void arrangeLayerArray(PHLMONITOR, const std::vector<PHLLSREF>&, bool, CBox*);\n    void renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry);\n    void renderWorkspaceWindowsFullscreen(PHLMONITOR, PHLWORKSPACE, const Time::steady_tp&); // renders workspace windows (fullscreen) (tiled, floating, pinned, but no special)\n    void renderWorkspaceWindows(PHLMONITOR, PHLWORKSPACE, const Time::steady_tp&);           // renders workspace windows (no fullscreen) (tiled, floating, pinned, but no special)\n    void renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const Vector2D& translate = {0, 0}, const float& scale = 1.f);\n    void renderWindow(PHLWINDOW, PHLMONITOR, const Time::steady_tp&, bool, eRenderPassMode, bool ignorePosition = false, bool standalone = false);\n    void renderLayer(PHLLS, PHLMONITOR, const Time::steady_tp&, bool popups = false, bool lockscreen = false);\n    void renderSessionLockSurface(WP<SSessionLockSurface>, PHLMONITOR, const Time::steady_tp&);\n    void renderDragIcon(PHLMONITOR, const Time::steady_tp&);\n    void renderIMEPopup(CInputPopup*, PHLMONITOR, const Time::steady_tp&);\n    void sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now); // sends frame displayed events but doesn't actually render anything\n    void renderSessionLockPrimer(PHLMONITOR pMonitor);\n    void renderSessionLockMissing(PHLMONITOR pMonitor);\n    void renderBackground(PHLMONITOR pMonitor);\n    void requestBackgroundResource();\n    std::string                       resolveAssetPath(const std::string& file);\n    void                              initMissingAssetTexture();\n    void                              initAssets();\n    SP<ITexture>                      m_missingAssetTexture;\n    ASP<Hyprgraphics::CImageResource> m_backgroundResource;\n    bool                              m_backgroundResourceFailed = false;\n\n    bool                              shouldBlur(PHLLS ls);\n    bool                              shouldBlur(PHLWINDOW w);\n    bool                              shouldBlur(WP<Desktop::View::CPopup> p);\n\n    bool                              m_cursorHidden            = false;\n    bool                              m_cursorHiddenByCondition = false;\n    bool                              m_cursorHasSurface        = false;\n    SP<Aquamarine::IBuffer>           m_currentBuffer           = nullptr;\n    eRenderMode                       m_renderMode              = RENDER_MODE_NORMAL;\n    bool                              m_nvidia                  = false;\n    bool                              m_intel                   = false;\n    bool                              m_software                = false;\n    bool                              m_mgpu                    = false;\n\n    struct {\n        bool hiddenOnTouch    = false;\n        bool hiddenOnTablet   = false;\n        bool hiddenOnTimeout  = false;\n        bool hiddenOnKeyboard = false;\n    } m_cursorHiddenConditions;\n\n    std::vector<SP<IRenderbuffer>> m_renderbuffers;\n    std::vector<PHLWINDOWREF>      m_renderUnfocused;\n    SP<CEventLoopTimer>            m_renderUnfocusedTimer;\n\n    friend class CRenderPass;\n    friend class CHyprOpenGLImpl;\n    friend class CToplevelExportFrame;\n    friend class Screenshare::CScreenshareFrame;\n    friend class CInputManager;\n    friend class CPointerManager;\n    friend class CMonitor;\n    friend class CMonitorFrameScheduler;\n\n  private:\n    void bindOffMain();\n    void bindBackOnMain();\n\n    void drawRect(CRectPassElement* element, const CRegion& damage);\n    void drawHints(CRendererHintsPassElement* element, const CRegion& damage);\n    void drawPreBlur(CPreBlurElement* element, const CRegion& damage);\n    void drawSurface(CSurfacePassElement* element, const CRegion& damage);\n    void preDrawSurface(CSurfacePassElement* element, const CRegion& damage);\n    void drawTex(CTexPassElement* element, const CRegion& damage);\n    void drawTexMatte(CTextureMatteElement* element, const CRegion& damage);\n};\n\ninline UP<IHyprRenderer> g_pHyprRenderer;\n"
  },
  {
    "path": "src/render/Shader.cpp",
    "content": "#include \"Shader.hpp\"\n#include \"../config/ConfigManager.hpp\"\n#include \"OpenGL.hpp\"\n\n#define EPSILON(x, y) (std::abs((x) - (y)) < 1e-5f)\n\nstatic bool compareFloat(auto a, auto b) {\n    if (a.size() != b.size())\n        return false;\n\n    for (size_t i = 0; i < a.size(); ++i)\n        if (std::fabs(a[i] - b[i]) > 1e-5f)\n            return false;\n\n    return true;\n}\n\nCShader::CShader() {\n    m_uniformLocations.fill(-1);\n}\n\nCShader::~CShader() {\n    destroy();\n}\n\nvoid CShader::logShaderError(const GLuint& shader, bool program, bool silent) {\n    GLint maxLength = 0;\n    if (program)\n        glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength);\n    else\n        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);\n\n    std::vector<GLchar> errorLog(maxLength);\n    if (program)\n        glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data());\n    else\n        glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data());\n    std::string errorStr(errorLog.begin(), errorLog.end());\n\n    const auto  FULLERROR = (program ? \"Screen shader parser: Error linking program:\" : \"Screen shader parser: Error compiling shader: \") + errorStr;\n\n    Log::logger->log(Log::ERR, \"Failed to link shader: {}\", FULLERROR);\n\n    if (!silent)\n        g_pConfigManager->addParseError(FULLERROR);\n}\n\nGLuint CShader::compileShader(const GLuint& type, std::string src, bool dynamic, bool silent) {\n    auto shader = glCreateShader(type);\n\n    auto shaderSource = src.c_str();\n\n    glShaderSource(shader, 1, &shaderSource, nullptr);\n    glCompileShader(shader);\n\n    GLint ok;\n    glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);\n\n    if (dynamic) {\n        if (ok == GL_FALSE) {\n            logShaderError(shader, false, silent);\n            return 0;\n        }\n    } else {\n        if (ok != GL_TRUE)\n            logShaderError(shader, false);\n        RASSERT(ok != GL_FALSE, \"compileShader() failed! GL_COMPILE_STATUS not OK!\");\n    }\n\n    return shader;\n}\n\nbool CShader::createProgram(const std::string& vert, const std::string& frag, bool dynamic, bool silent) {\n    auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert, dynamic, silent);\n    if (dynamic) {\n        if (vertCompiled == 0)\n            return false;\n    } else\n        RASSERT(vertCompiled, \"Compiling shader failed. VERTEX nullptr! Shader source:\\n\\n{}\", vert);\n\n    auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag, dynamic, silent);\n    if (dynamic) {\n        if (fragCompiled == 0)\n            return false;\n    } else\n        RASSERT(fragCompiled, \"Compiling shader failed. FRAGMENT nullptr! Shader source:\\n\\n{}\", frag);\n\n    auto prog = glCreateProgram();\n    glAttachShader(prog, vertCompiled);\n    glAttachShader(prog, fragCompiled);\n    glLinkProgram(prog);\n\n    glDetachShader(prog, vertCompiled);\n    glDetachShader(prog, fragCompiled);\n    glDeleteShader(vertCompiled);\n    glDeleteShader(fragCompiled);\n\n    GLint ok;\n    glGetProgramiv(prog, GL_LINK_STATUS, &ok);\n    if (dynamic) {\n        if (ok == GL_FALSE) {\n            logShaderError(prog, true, silent);\n            return false;\n        }\n    } else {\n        if (ok != GL_TRUE)\n            logShaderError(prog, true);\n        RASSERT(ok != GL_FALSE, \"createProgram() failed! GL_LINK_STATUS not OK!\");\n    }\n\n    m_program = prog;\n\n    getUniformLocations();\n    createVao();\n    return true;\n}\n\n// its fine to call glGet on shaders that dont have the uniform\n// this however hardcodes the name now. #TODO maybe dont\nvoid CShader::getUniformLocations() {\n    auto getUniform = [this](const GLchar* name) { return glGetUniformLocation(m_program, name); };\n    auto getAttrib  = [this](const GLchar* name) { return glGetAttribLocation(m_program, name); };\n\n    m_uniformLocations[SHADER_PROJ]        = getUniform(\"proj\");\n    m_uniformLocations[SHADER_COLOR]       = getUniform(\"color\");\n    m_uniformLocations[SHADER_ALPHA_MATTE] = getUniform(\"texMatte\");\n    m_uniformLocations[SHADER_TEX_TYPE]    = getUniform(\"texType\");\n\n    // shader has #include \"CM.glsl\"\n    m_uniformLocations[SHADER_SOURCE_TF]            = getUniform(\"sourceTF\");\n    m_uniformLocations[SHADER_TARGET_TF]            = getUniform(\"targetTF\");\n    m_uniformLocations[SHADER_SRC_TF_RANGE]         = getUniform(\"srcTFRange\");\n    m_uniformLocations[SHADER_DST_TF_RANGE]         = getUniform(\"dstTFRange\");\n    m_uniformLocations[SHADER_TARGET_PRIMARIES_XYZ] = getUniform(\"targetPrimariesXYZ\");\n    m_uniformLocations[SHADER_MAX_LUMINANCE]        = getUniform(\"maxLuminance\");\n    m_uniformLocations[SHADER_SRC_REF_LUMINANCE]    = getUniform(\"srcRefLuminance\");\n    m_uniformLocations[SHADER_DST_MAX_LUMINANCE]    = getUniform(\"dstMaxLuminance\");\n    m_uniformLocations[SHADER_DST_REF_LUMINANCE]    = getUniform(\"dstRefLuminance\");\n    m_uniformLocations[SHADER_SDR_SATURATION]       = getUniform(\"sdrSaturation\");\n    m_uniformLocations[SHADER_SDR_BRIGHTNESS]       = getUniform(\"sdrBrightnessMultiplier\");\n    m_uniformLocations[SHADER_CONVERT_MATRIX]       = getUniform(\"convertMatrix\");\n    m_uniformLocations[SHADER_LUT_3D]               = getUniform(\"iccLut3D\");\n    m_uniformLocations[SHADER_LUT_SIZE]             = getUniform(\"iccLutSize\");\n    //\n    m_uniformLocations[SHADER_TEX]                 = getUniform(\"tex\");\n    m_uniformLocations[SHADER_BLURRED_BG]          = getUniform(\"blurredBG\");\n    m_uniformLocations[SHADER_UV_SIZE]             = getUniform(\"uvSize\");\n    m_uniformLocations[SHADER_UV_OFFSET]           = getUniform(\"uvOffset\");\n    m_uniformLocations[SHADER_ALPHA]               = getUniform(\"alpha\");\n    m_uniformLocations[SHADER_POS_ATTRIB]          = getAttrib(\"pos\");\n    m_uniformLocations[SHADER_TEX_ATTRIB]          = getAttrib(\"texcoord\");\n    m_uniformLocations[SHADER_MATTE_TEX_ATTRIB]    = getAttrib(\"texcoordMatte\");\n    m_uniformLocations[SHADER_DISCARD_OPAQUE]      = getUniform(\"discardOpaque\");\n    m_uniformLocations[SHADER_DISCARD_ALPHA]       = getUniform(\"discardAlpha\");\n    m_uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = getUniform(\"discardAlphaValue\");\n    /* set in createVao\n        m_uniformLocations[SHADER_SHADER_VAO]\n        m_uniformLocations[SHADER_SHADER_VBO_POS]\n        m_uniformLocations[SHADER_SHADER_VBO_UV]\n        */\n    m_uniformLocations[SHADER_TOP_LEFT]     = getUniform(\"topLeft\");\n    m_uniformLocations[SHADER_BOTTOM_RIGHT] = getUniform(\"bottomRight\");\n\n    // compat for screenshaders\n    auto fullSize = getUniform(\"fullSize\");\n    if (fullSize == -1)\n        fullSize = getUniform(\"screen_size\");\n    if (fullSize == -1)\n        fullSize = getUniform(\"screenSize\");\n    m_uniformLocations[SHADER_FULL_SIZE] = fullSize;\n\n    m_uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED]   = getUniform(\"fullSizeUntransformed\");\n    m_uniformLocations[SHADER_RADIUS]                    = getUniform(\"radius\");\n    m_uniformLocations[SHADER_RADIUS_OUTER]              = getUniform(\"radiusOuter\");\n    m_uniformLocations[SHADER_ROUNDING_POWER]            = getUniform(\"roundingPower\");\n    m_uniformLocations[SHADER_THICK]                     = getUniform(\"thick\");\n    m_uniformLocations[SHADER_HALFPIXEL]                 = getUniform(\"halfpixel\");\n    m_uniformLocations[SHADER_RANGE]                     = getUniform(\"range\");\n    m_uniformLocations[SHADER_SHADOW_POWER]              = getUniform(\"shadowPower\");\n    m_uniformLocations[SHADER_USE_ALPHA_MATTE]           = getUniform(\"useAlphaMatte\");\n    m_uniformLocations[SHADER_APPLY_TINT]                = getUniform(\"applyTint\");\n    m_uniformLocations[SHADER_TINT]                      = getUniform(\"tint\");\n    m_uniformLocations[SHADER_GRADIENT]                  = getUniform(\"gradient\");\n    m_uniformLocations[SHADER_GRADIENT_LENGTH]           = getUniform(\"gradientLength\");\n    m_uniformLocations[SHADER_GRADIENT2]                 = getUniform(\"gradient2\");\n    m_uniformLocations[SHADER_GRADIENT2_LENGTH]          = getUniform(\"gradient2Length\");\n    m_uniformLocations[SHADER_ANGLE]                     = getUniform(\"angle\");\n    m_uniformLocations[SHADER_ANGLE2]                    = getUniform(\"angle2\");\n    m_uniformLocations[SHADER_GRADIENT_LERP]             = getUniform(\"gradientLerp\");\n    m_uniformLocations[SHADER_TIME]                      = getUniform(\"time\");\n    m_uniformLocations[SHADER_DISTORT]                   = getUniform(\"distort\");\n    m_uniformLocations[SHADER_WL_OUTPUT]                 = getUniform(\"wl_output\");\n    m_uniformLocations[SHADER_CONTRAST]                  = getUniform(\"contrast\");\n    m_uniformLocations[SHADER_PASSES]                    = getUniform(\"passes\");\n    m_uniformLocations[SHADER_VIBRANCY]                  = getUniform(\"vibrancy\");\n    m_uniformLocations[SHADER_VIBRANCY_DARKNESS]         = getUniform(\"vibrancy_darkness\");\n    m_uniformLocations[SHADER_BRIGHTNESS]                = getUniform(\"brightness\");\n    m_uniformLocations[SHADER_NOISE]                     = getUniform(\"noise\");\n    m_uniformLocations[SHADER_POINTER]                   = getUniform(\"pointer_position\");\n    m_uniformLocations[SHADER_POINTER_SHAPE]             = getUniform(\"pointer_shape\");\n    m_uniformLocations[SHADER_POINTER_SWITCH_TIME]       = getUniform(\"pointer_switch_time\");\n    m_uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS]    = getUniform(\"pointer_shape_previous\");\n    m_uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = getUniform(\"pointer_pressed_positions\");\n    m_uniformLocations[SHADER_POINTER_HIDDEN]            = getUniform(\"pointer_hidden\");\n    m_uniformLocations[SHADER_POINTER_KILLING]           = getUniform(\"pointer_killing\");\n    m_uniformLocations[SHADER_POINTER_PRESSED_TIMES]     = getUniform(\"pointer_pressed_times\");\n    m_uniformLocations[SHADER_POINTER_PRESSED_KILLED]    = getUniform(\"pointer_pressed_killed\");\n    m_uniformLocations[SHADER_POINTER_PRESSED_TOUCHED]   = getUniform(\"pointer_pressed_touched\");\n    m_uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT]  = getUniform(\"pointer_inactive_timeout\");\n    m_uniformLocations[SHADER_POINTER_LAST_ACTIVE]       = getUniform(\"pointer_last_active\");\n    m_uniformLocations[SHADER_POINTER_SIZE]              = getUniform(\"pointer_size\");\n}\n\nvoid CShader::createVao() {\n    GLuint shaderVao = 0, shaderVbo = 0;\n\n    glGenVertexArrays(1, &shaderVao);\n    glBindVertexArray(shaderVao);\n\n    if (m_uniformLocations[SHADER_POS_ATTRIB] != -1) {\n        glGenBuffers(1, &shaderVbo);\n        glBindBuffer(GL_ARRAY_BUFFER, shaderVbo);\n        glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts.data(), GL_DYNAMIC_DRAW);\n        glEnableVertexAttribArray(m_uniformLocations[SHADER_POS_ATTRIB]);\n        glVertexAttribPointer(m_uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, x));\n    }\n\n    // UV VBO (dynamic, may be updated per frame)\n    if (m_uniformLocations[SHADER_TEX_ATTRIB] != -1 && shaderVbo != 0) {\n        glBindBuffer(GL_ARRAY_BUFFER, shaderVbo);\n        glEnableVertexAttribArray(m_uniformLocations[SHADER_TEX_ATTRIB]);\n        glVertexAttribPointer(m_uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, u));\n    }\n\n    glBindVertexArray(0);\n    glBindBuffer(GL_ARRAY_BUFFER, 0);\n\n    m_uniformLocations[SHADER_SHADER_VAO] = shaderVao;\n    m_uniformLocations[SHADER_SHADER_VBO] = shaderVbo;\n\n    RASSERT(m_uniformLocations[SHADER_SHADER_VAO] >= 0, \"SHADER_SHADER_VAO could not be created\");\n    RASSERT(m_uniformLocations[SHADER_SHADER_VBO] >= 0, \"SHADER_SHADER_VBO_POS could not be created\");\n}\n\nvoid CShader::setUniformInt(eShaderUniform location, GLint v0) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0 && std::get<GLint>(cached) == v0)\n        return;\n\n    cached = v0;\n\n    GLCALL(glUniform1i(m_uniformLocations[location], v0));\n}\n\nvoid CShader::setUniformFloat(eShaderUniform location, GLfloat v0) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<GLfloat>(cached);\n        if (EPSILON(val, v0))\n            return;\n    }\n\n    cached = v0;\n    GLCALL(glUniform1f(m_uniformLocations[location], v0));\n}\n\nvoid CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<std::array<GLfloat, 2>>(cached);\n        if (EPSILON(val[0], v0) && EPSILON(val[1], v1))\n            return;\n    }\n\n    cached = std::array<GLfloat, 2>{v0, v1};\n    GLCALL(glUniform2f(m_uniformLocations[location], v0, v1));\n}\n\nvoid CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<std::array<GLfloat, 3>>(cached);\n        if (EPSILON(val[0], v0) && EPSILON(val[1], v1) && EPSILON(val[2], v2))\n            return;\n    }\n\n    cached = std::array<GLfloat, 3>{v0, v1, v2};\n    GLCALL(glUniform3f(m_uniformLocations[location], v0, v1, v2));\n}\n\nvoid CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<std::array<GLfloat, 4>>(cached);\n        if (EPSILON(val[0], v0) && EPSILON(val[1], v1) && EPSILON(val[2], v2) && EPSILON(val[3], v3))\n            return;\n    }\n\n    cached = std::array<GLfloat, 4>{v0, v1, v2, v3};\n    GLCALL(glUniform4f(m_uniformLocations[location], v0, v1, v2, v3));\n}\n\nvoid CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array<GLfloat, 9> value) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<SUniformMatrix3Data>(cached);\n        if (val.count == count && val.transpose == transpose && compareFloat(val.value, value))\n            return;\n    }\n\n    cached = SUniformMatrix3Data{.count = count, .transpose = transpose, .value = value};\n    GLCALL(glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data()));\n}\n\nvoid CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array<GLfloat, 8> value) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<SUniformMatrix4Data>(cached);\n        if (val.count == count && val.transpose == transpose && compareFloat(val.value, value))\n            return;\n    }\n\n    cached = SUniformMatrix4Data{.count = count, .transpose = transpose, .value = value};\n    GLCALL(glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data()));\n}\n\nvoid CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector<float>& value, GLsizei vec_size) {\n    if (m_uniformLocations.at(location) == -1)\n        return;\n\n    auto& cached = uniformStatus.at(location);\n\n    if (cached.index() != 0) {\n        auto val = std::get<SUniformVData>(cached);\n        if (val.count == count && compareFloat(val.value, value))\n            return;\n    }\n\n    cached = SUniformVData{.count = count, .value = value};\n    switch (vec_size) {\n        case 1: GLCALL(glUniform1fv(m_uniformLocations[location], count, value.data())); break;\n        case 2: GLCALL(glUniform2fv(m_uniformLocations[location], count, value.data())); break;\n        case 4: GLCALL(glUniform4fv(m_uniformLocations[location], count, value.data())); break;\n        default: UNREACHABLE();\n    }\n}\n\nvoid CShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector<float>& value) {\n    setUniformfv(location, count, value, 1);\n}\n\nvoid CShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector<float>& value) {\n    setUniformfv(location, count, value, 2);\n}\n\nvoid CShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector<float>& value) {\n    setUniformfv(location, count, value, 4);\n}\n\nvoid CShader::destroy() {\n    uniformStatus.fill(std::monostate());\n\n    if (m_program == 0)\n        return;\n\n    GLuint shaderVao, shaderVbo;\n\n    shaderVao = m_uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VAO];\n    shaderVbo = m_uniformLocations[SHADER_SHADER_VBO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VBO];\n\n    if (shaderVao)\n        glDeleteVertexArrays(1, &shaderVao);\n\n    if (shaderVbo)\n        glDeleteBuffers(1, &shaderVbo);\n\n    glDeleteProgram(m_program);\n    m_program = 0;\n}\n\nGLint CShader::getUniformLocation(eShaderUniform location) const {\n    return m_uniformLocations[location];\n}\n\nGLuint CShader::program() const {\n    return m_program;\n}\n\nint CShader::getInitialTime() const {\n    return m_initialTime;\n}\n\nvoid CShader::setInitialTime(int time) {\n    m_initialTime = time;\n}\n"
  },
  {
    "path": "src/render/Shader.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include <array>\n#include <variant>\n\nenum eShaderUniform : uint8_t {\n    SHADER_PROJ = 0,\n    SHADER_COLOR,\n    SHADER_ALPHA_MATTE,\n    SHADER_TEX_TYPE,\n    SHADER_SOURCE_TF,\n    SHADER_TARGET_TF,\n    SHADER_SRC_TF_RANGE,\n    SHADER_DST_TF_RANGE,\n    SHADER_TARGET_PRIMARIES_XYZ,\n    SHADER_MAX_LUMINANCE,\n    SHADER_SRC_REF_LUMINANCE,\n    SHADER_DST_MAX_LUMINANCE,\n    SHADER_DST_REF_LUMINANCE,\n    SHADER_SDR_SATURATION,\n    SHADER_SDR_BRIGHTNESS,\n    SHADER_CONVERT_MATRIX,\n    SHADER_TEX,\n    SHADER_ALPHA,\n    SHADER_POS_ATTRIB,\n    SHADER_TEX_ATTRIB,\n    SHADER_MATTE_TEX_ATTRIB,\n    SHADER_DISCARD_OPAQUE,\n    SHADER_DISCARD_ALPHA,\n    SHADER_DISCARD_ALPHA_VALUE,\n    SHADER_SHADER_VAO,\n    SHADER_SHADER_VBO,\n    SHADER_TOP_LEFT,\n    SHADER_BOTTOM_RIGHT,\n    SHADER_FULL_SIZE,\n    SHADER_FULL_SIZE_UNTRANSFORMED,\n    SHADER_RADIUS,\n    SHADER_RADIUS_OUTER,\n    SHADER_ROUNDING_POWER,\n    SHADER_THICK,\n    SHADER_HALFPIXEL,\n    SHADER_RANGE,\n    SHADER_SHADOW_POWER,\n    SHADER_USE_ALPHA_MATTE,\n    SHADER_APPLY_TINT,\n    SHADER_TINT,\n    SHADER_GRADIENT,\n    SHADER_GRADIENT_LENGTH,\n    SHADER_ANGLE,\n    SHADER_GRADIENT2,\n    SHADER_GRADIENT2_LENGTH,\n    SHADER_ANGLE2,\n    SHADER_GRADIENT_LERP,\n    SHADER_TIME,\n    SHADER_DISTORT,\n    SHADER_WL_OUTPUT,\n    SHADER_CONTRAST,\n    SHADER_PASSES,\n    SHADER_VIBRANCY,\n    SHADER_VIBRANCY_DARKNESS,\n    SHADER_BRIGHTNESS,\n    SHADER_NOISE,\n    SHADER_POINTER,\n    SHADER_POINTER_SHAPE,\n    SHADER_POINTER_SWITCH_TIME,\n    SHADER_POINTER_SHAPE_PREVIOUS,\n    SHADER_POINTER_PRESSED_POSITIONS,\n    SHADER_POINTER_HIDDEN,\n    SHADER_POINTER_KILLING,\n    SHADER_POINTER_PRESSED_TIMES,\n    SHADER_POINTER_PRESSED_KILLED,\n    SHADER_POINTER_PRESSED_TOUCHED,\n    SHADER_POINTER_INACTIVE_TIMEOUT,\n    SHADER_POINTER_LAST_ACTIVE,\n    SHADER_POINTER_SIZE,\n    SHADER_LUT_3D,\n    SHADER_LUT_SIZE,\n    SHADER_BLURRED_BG,\n    SHADER_UV_SIZE,\n    SHADER_UV_OFFSET,\n\n    SHADER_LAST,\n};\n\nclass CShader {\n  public:\n    CShader();\n    ~CShader();\n\n    bool   createProgram(const std::string& vert, const std::string& frag, bool dynamic = false, bool silent = false);\n    void   setUniformInt(eShaderUniform location, GLint v0);\n    void   setUniformFloat(eShaderUniform location, GLfloat v0);\n    void   setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1);\n    void   setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2);\n    void   setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);\n    void   setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array<GLfloat, 9> value);\n    void   setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array<GLfloat, 8> value);\n    void   setUniform1fv(eShaderUniform location, GLsizei count, const std::vector<float>& value);\n    void   setUniform2fv(eShaderUniform location, GLsizei count, const std::vector<float>& value);\n    void   setUniform4fv(eShaderUniform location, GLsizei count, const std::vector<float>& value);\n    void   destroy();\n    GLuint program() const;\n    GLint  getUniformLocation(eShaderUniform location) const;\n    int    getInitialTime() const;\n    void   setInitialTime(int time);\n\n  private:\n    GLuint                         m_program     = 0;\n    float                          m_initialTime = 0;\n    std::array<GLint, SHADER_LAST> m_uniformLocations;\n\n    struct SUniformMatrix3Data {\n        GLsizei                count     = 0;\n        GLboolean              transpose = false;\n        std::array<GLfloat, 9> value     = {};\n    };\n\n    struct SUniformMatrix4Data {\n        GLsizei                count     = 0;\n        GLboolean              transpose = false;\n        std::array<GLfloat, 8> value     = {};\n    };\n\n    struct SUniformVData {\n        GLsizei            count = 0;\n        std::vector<float> value;\n    };\n\n    //\n    std::array<std::variant<std::monostate, GLint, GLfloat, std::array<GLfloat, 2>, std::array<GLfloat, 3>, std::array<GLfloat, 4>, SUniformMatrix3Data, SUniformMatrix4Data,\n                            SUniformVData>,\n               SHADER_LAST>\n        uniformStatus;\n    //\n\n    void   logShaderError(const GLuint&, bool program = false, bool silent = false);\n    GLuint compileShader(const GLuint&, std::string, bool dynamic = false, bool silent = false);\n    void   getUniformLocations();\n    void   createVao();\n    void   setUniformfv(eShaderUniform location, GLsizei count, const std::vector<float>& value, GLsizei vec_size);\n};\n"
  },
  {
    "path": "src/render/ShaderLoader.cpp",
    "content": "#include \"ShaderLoader.hpp\"\n#include <format>\n#include <hyprutils/memory/Casts.hpp>\n#include <hyprutils/memory/UniquePtr.hpp>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/path/Path.hpp>\n#include \"../debug/log/Logger.hpp\"\n#include \"shaders/Shaders.hpp\"\n#include \"../helpers/fs/FsUtils.hpp\"\n#include \"Renderer.hpp\"\n#include <glslang/Public/resource_limits_c.h>\n#include <string>\n#include <filesystem>\n\nusing namespace Render;\n\nusing namespace Render;\n\nCShaderLoader::CShaderLoader(const std::vector<std::string> includes, const std::array<std::string, SH_FRAG_LAST>& frags, const std::string shaderPath) : m_shaderPath(shaderPath) {\n    m_callbacks = glsl_include_callbacks_t{\n        .include_local =\n            [](void* ctx, const char* header_name, const char* includer_name, size_t include_depth) {\n                auto shaderLoader = sc<CShaderLoader*>(ctx);\n                auto res          = new glsl_include_result_t;\n                if (shaderLoader->m_overrideDefines.length() && std::string{header_name} == \"defines.h\") {\n                    res->header_name   = header_name;\n                    res->header_data   = shaderLoader->m_overrideDefines.c_str();\n                    res->header_length = shaderLoader->m_overrideDefines.length();\n                } else if (shaderLoader->includes().contains(header_name)) {\n                    res->header_name   = header_name;\n                    res->header_data   = shaderLoader->includes().at(header_name).c_str();\n                    res->header_length = shaderLoader->includes().at(header_name).length();\n                } else {\n                    res->header_name   = nullptr;\n                    res->header_data   = nullptr;\n                    res->header_length = 0;\n                }\n\n                shaderLoader->m_includeResults.push_back(res);\n                return res;\n            },\n        .free_include_result =\n            [](void* ctx, glsl_include_result_t* result) {\n                auto shaderLoader = sc<CShaderLoader*>(ctx);\n                std::erase(shaderLoader->m_includeResults, result);\n                delete result;\n                return 0;\n            },\n    };\n\n    for (const auto& inc : includes) {\n        include(inc);\n    }\n\n    std::ranges::transform(frags, m_fragFiles.begin(), [&](const auto& filename) { return loadShader(filename); });\n}\n\nCShaderLoader::~CShaderLoader() {\n    // glslFreeIncludeResult should leave it empty by this point\n    for (const auto& res : m_includeResults) {\n        delete res;\n    }\n}\n\nvoid CShaderLoader::include(const std::string& filename) {\n    m_includes.insert({filename, loadShader(filename)});\n}\n\nstd::string CShaderLoader::getDefines(ShaderFeatureFlags features) {\n    std::string                        res     = \"\";\n    std::map<std::string, std::string> defines = {\n        {\"USE_RGBA\", features & SH_FEAT_RGBA ? \"1\" : \"0\"},         {\"USE_DISCARD\", features & SH_FEAT_DISCARD ? \"1\" : \"0\"}, {\"USE_TINT\", features & SH_FEAT_TINT ? \"1\" : \"0\"},\n        {\"USE_ROUNDING\", features & SH_FEAT_ROUNDING ? \"1\" : \"0\"}, {\"USE_CM\", features & SH_FEAT_CM ? \"1\" : \"0\"},           {\"USE_TONEMAP\", features & SH_FEAT_TONEMAP ? \"1\" : \"0\"},\n        {\"USE_SDR_MOD\", features & SH_FEAT_SDR_MOD ? \"1\" : \"0\"},   {\"USE_BLUR\", features & SH_FEAT_BLUR ? \"1\" : \"0\"},       {\"USE_ICC\", features & SH_FEAT_ICC ? \"1\" : \"0\"},\n    };\n    for (const auto& [name, value] : defines) {\n        res += std::format(\"#define {} {}\\n\", name, value);\n    }\n    return res;\n}\n\nstd::string CShaderLoader::processSource(const std::string& source, glslang_stage_t stage) {\n    const glslang_input_t input = {\n        .language                          = GLSLANG_SOURCE_GLSL,\n        .stage                             = stage,\n        .client                            = GLSLANG_CLIENT_NONE,\n        .target_language                   = GLSLANG_TARGET_NONE,\n        .code                              = source.c_str(),\n        .default_version                   = 100,\n        .default_profile                   = GLSLANG_NO_PROFILE,\n        .force_default_version_and_profile = false,\n        .forward_compatible                = false,\n        .messages                          = GLSLANG_MSG_DEFAULT_BIT,\n        .resource                          = glslang_default_resource(),\n        .callbacks                         = m_callbacks,\n        .callbacks_ctx                     = this,\n    };\n\n    glslang_shader_t* shader = glslang_shader_create(&input);\n\n    if (!glslang_shader_preprocess(shader, &input)) {\n        Log::logger->log(Log::ERR, \"GLSL preprocessing failed\");\n        Log::logger->log(Log::ERR, \"{}\", glslang_shader_get_info_log(shader));\n        Log::logger->log(Log::ERR, \"{}\", glslang_shader_get_info_debug_log(shader));\n        Log::logger->log(Log::ERR, \"{}\", input.code);\n        glslang_shader_delete(shader);\n        return source;\n    }\n\n    std::stringstream stream(glslang_shader_get_preprocessed_code(shader));\n    std::string       code = \"\";\n    std::string       line;\n\n    while (std::getline(stream, line)) {\n        if (!line.starts_with(\"#line \"))\n            code += line + \"\\n\";\n    }\n\n    glslang_shader_delete(shader);\n    return code;\n}\n\nstd::string CShaderLoader::process(const std::string& filename) {\n    auto source = loadShader(filename);\n    return processSource(source, filename.ends_with(\".vert\") ? GLSLANG_STAGE_VERTEX : GLSLANG_STAGE_FRAGMENT);\n}\n\nstd::string CShaderLoader::process(const std::string& filename, const std::map<std::string, std::string>& defines) {\n    m_overrideDefines = \"\";\n    for (const auto& [name, value] : defines) {\n        m_overrideDefines += std::format(\"#define {} {}\\n\", name, value);\n    }\n    const auto& res   = process(filename);\n    m_overrideDefines = \"\";\n    return res;\n}\n\nstd::string CShaderLoader::getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features) {\n    static const auto PCM = CConfigValue<Hyprlang::INT>(\"render:cm_enabled\");\n    if (!*PCM)\n        features &= ~(SH_FEAT_CM | SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD);\n\n    if (!m_fragVariants[frag].contains(features)) {\n        ASSERT(m_fragFiles[frag].length());\n        m_overrideDefines              = getDefines(features);\n        m_fragVariants[frag][features] = processSource(m_fragFiles[frag]);\n        m_overrideDefines              = \"\";\n    }\n\n    return m_fragVariants[frag][features];\n}\n\nconst std::map<std::string, std::string>& CShaderLoader::includes() {\n    return m_includes;\n}\n\n// TODO notify user if bundled shader is newer than ~/.config override\nstd::string CShaderLoader::loadShader(const std::string& filename) {\n    if (m_shaderPath.length()) {\n        std::filesystem::path path = m_shaderPath;\n        const auto            src  = NFsUtils::readFileAsString(path / filename);\n        if (src.has_value())\n            return src.value();\n    }\n    const auto home = Hyprutils::Path::getHome();\n    if (home.has_value()) {\n        const auto src = NFsUtils::readFileAsString(home.value() + \"/hypr/shaders/\" + filename);\n        if (src.has_value())\n            return src.value();\n    }\n    for (auto& e : ASSET_PATHS) {\n        const auto src = NFsUtils::readFileAsString(std::string{e} + \"/hypr/shaders/\" + filename);\n        if (src.has_value())\n            return src.value();\n    }\n    if (SHADERS.contains(filename))\n        return SHADERS.at(filename);\n    throw std::runtime_error(std::format(\"Couldn't load shader {}\", filename));\n}\n"
  },
  {
    "path": "src/render/ShaderLoader.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <glslang/Include/glslang_c_interface.h>\n#include <string>\n#include <vector>\n#include <map>\n#include \"../helpers/memory/Memory.hpp\"\n\nnamespace Render {\n    enum ePreparedFragmentShaderFeature : uint16_t {\n        SH_FEAT_UNKNOWN = 0, // all features just in case\n\n        SH_FEAT_RGBA     = (1 << 0), // RGBA/RGBX texture sampling\n        SH_FEAT_DISCARD  = (1 << 1), // RGBA/RGBX texture sampling\n        SH_FEAT_TINT     = (1 << 2), // uniforms: tint; condition: applyTint\n        SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0\n        SH_FEAT_CM       = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM\n        SH_FEAT_TONEMAP  = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01\n        SH_FEAT_SDR_MOD  = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1)\n        SH_FEAT_BLUR     = (1 << 7), // condition: render:use_shader_blur_blend\n        SH_FEAT_ICC      = (1 << 8), //\n\n        // uniforms: targetPrimariesXYZ; condition: SH_FEAT_TONEMAP || SH_FEAT_SDR_MOD\n    };\n\n    using ShaderFeatureFlags = uint16_t;\n\n    enum ePreparedFragmentShader : uint8_t {\n        SH_FRAG_QUAD = 0,\n        SH_FRAG_PASSTHRURGBA,\n        SH_FRAG_MATTE,\n        SH_FRAG_EXT,\n        SH_FRAG_BLUR1,\n        SH_FRAG_BLUR2,\n        SH_FRAG_BLURPREPARE,\n        SH_FRAG_BLURFINISH,\n        SH_FRAG_SHADOW,\n        SH_FRAG_SURFACE,\n        SH_FRAG_BORDER1,\n        SH_FRAG_GLITCH,\n\n        SH_FRAG_LAST,\n    };\n\n    class CShaderLoader {\n      public:\n        CShaderLoader(const std::vector<std::string> includes, const std::array<std::string, SH_FRAG_LAST>& frags, const std::string shaderPath = \"\");\n        ~CShaderLoader();\n\n        void                                      include(const std::string& filename);\n        std::string                               process(const std::string& filename);\n        std::string                               process(const std::string& filename, const std::map<std::string, std::string>& defines);\n\n        std::string                               getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features);\n\n        const std::map<std::string, std::string>& includes();\n\n        std::vector<glsl_include_result_t*>       m_includeResults;\n\n      private:\n        std::string loadShader(const std::string& filename);\n        std::string getDefines(ShaderFeatureFlags features);\n        std::string processSource(const std::string& source, glslang_stage_t stage = GLSLANG_STAGE_FRAGMENT);\n\n        //\n        std::string                                                         m_shaderPath;\n        std::array<std::string, SH_FRAG_LAST>                               m_fragFiles;\n        std::array<std::map<ShaderFeatureFlags, std::string>, SH_FRAG_LAST> m_fragVariants;\n        std::map<std::string, std::string>                                  m_includes;\n\n        std::string                                                         m_overrideDefines;\n        glsl_include_callbacks_t                                            m_callbacks;\n    };\n\n    inline UP<CShaderLoader> g_pShaderLoader;\n}\n"
  },
  {
    "path": "src/render/Texture.cpp",
    "content": "#include \"Texture.hpp\"\n#include <cstring>\n\nITexture::ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) :\n    m_size(size), m_opaque(opaque), m_drmFormat(drmFormat), m_keepDataCopy(keepDataCopy) {\n    if (m_keepDataCopy && stride && pixels) {\n        m_dataCopy.resize(stride * size.y);\n        memcpy(m_dataCopy.data(), pixels, stride * size.y);\n    }\n}\n\nITexture::ITexture(std::span<const float> lut3D, size_t N) : m_type(TEXTURE_3D_LUT), m_size(lut3D.size() / 3, 1), m_isSynchronous(true) {}\n\nbool ITexture::ok() {\n    return false;\n}\n\nbool ITexture::isDMA() {\n    return false;\n}\n\nconst std::vector<uint8_t>& ITexture::dataCopy() {\n    return m_dataCopy;\n}\n"
  },
  {
    "path": "src/render/Texture.hpp",
    "content": "#pragma once\n\n#include \"../defines.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n#include <hyprutils/math/Misc.hpp>\n#include <span>\n\nclass IHLBuffer;\nHYPRUTILS_FORWARD(Math, CRegion);\n\nenum eTextureType : int8_t {\n    TEXTURE_INVALID = -1, // Invalid\n    TEXTURE_RGBA    = 0,  // 4 channels\n    TEXTURE_RGBX,         // discard A\n    TEXTURE_3D_LUT,       // 3D LUT\n    TEXTURE_EXTERNAL,     // EGLImage\n};\n\nclass ITexture {\n  public:\n    ITexture(ITexture&)        = delete;\n    ITexture(ITexture&&)       = delete;\n    ITexture(const ITexture&&) = delete;\n    ITexture(const ITexture&)  = delete;\n\n    virtual ~ITexture() = default;\n\n    virtual void                setTexParameter(GLenum pname, GLint param)                                          = 0;\n    virtual void                allocate(const Vector2D& size, uint32_t drmFormat = 0)                              = 0;\n    virtual void                update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) = 0;\n    virtual void                bind() {};\n    virtual void                unbind() {};\n    virtual bool                ok();\n    virtual bool                isDMA();\n\n    const std::vector<uint8_t>& dataCopy();\n\n    eTextureType                m_type      = TEXTURE_RGBA;\n    Vector2D                    m_size      = {};\n    eTransform                  m_transform = HYPRUTILS_TRANSFORM_NORMAL;\n    bool                        m_opaque    = false;\n\n    uint32_t                    m_drmFormat     = 0; // for shm\n    bool                        m_isSynchronous = false;\n\n    // TODO move to GLTexture\n    GLuint m_texID   = 0;\n    GLenum magFilter = GL_LINEAR; // useNearestNeighbor overwrites these\n    GLenum minFilter = GL_LINEAR;\n\n  protected:\n    ITexture() = default;\n    ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false);\n    ITexture(std::span<const float> lut3D, size_t N);\n\n    bool                 m_keepDataCopy = false;\n    std::vector<uint8_t> m_dataCopy;\n};\n"
  },
  {
    "path": "src/render/Transformer.cpp",
    "content": "#include \"Transformer.hpp\"\n\nvoid IWindowTransformer::preWindowRender(CSurfacePassElement::SRenderData* pRenderData) {\n    ;\n}"
  },
  {
    "path": "src/render/Transformer.hpp",
    "content": "#pragma once\n\n#include \"Framebuffer.hpp\"\n#include \"pass/SurfacePassElement.hpp\"\n\n// A window transformer can be attached to a window.\n// If any is attached, Hyprland will render the window to a separate fb, then call the transform() func with it,\n// and finally render it back to the main fb after all transformers pass.\n//\n// Worth noting transformers for now only affect the main pass (not popups)\nclass IWindowTransformer {\n  public:\n    virtual ~IWindowTransformer() = default;\n\n    // called by Hyprland. For more data about what is being rendered, inspect render data.\n    // returns the out fb.\n    virtual IFramebuffer* transform(IFramebuffer* in) = 0;\n\n    // called by Hyprland before a window main pass is started.\n    virtual void preWindowRender(CSurfacePassElement::SRenderData* pRenderData);\n};"
  },
  {
    "path": "src/render/decorations/CHyprBorderDecoration.cpp",
    "content": "#include \"CHyprBorderDecoration.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../pass/BorderPassElement.hpp\"\n#include \"../Renderer.hpp\"\n\nCHyprBorderDecoration::CHyprBorderDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) {\n    ;\n}\n\nSDecorationPositioningInfo CHyprBorderDecoration::getPositioningInfo() {\n    const auto BORDERSIZE = m_window->getRealBorderSize();\n    m_extents             = {{BORDERSIZE, BORDERSIZE}, {BORDERSIZE, BORDERSIZE}};\n\n    if (doesntWantBorders())\n        m_extents = {{}, {}};\n\n    SDecorationPositioningInfo info;\n    info.priority       = 10000;\n    info.policy         = DECORATION_POSITION_STICKY;\n    info.desiredExtents = m_extents;\n    info.reserved       = true;\n    info.edges          = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP;\n\n    m_reportedExtents = m_extents;\n    return info;\n}\n\nvoid CHyprBorderDecoration::onPositioningReply(const SDecorationPositioningReply& reply) {\n    m_assignedGeometry = reply.assignedGeometry;\n}\n\nCBox CHyprBorderDecoration::assignedBoxGlobal() {\n    CBox box = m_assignedGeometry;\n    box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP, m_window));\n\n    const auto PWORKSPACE = m_window->m_workspace;\n\n    if (!PWORKSPACE)\n        return box;\n\n    const auto WORKSPACEOFFSET = PWORKSPACE && !m_window->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D();\n    return box.translate(WORKSPACEOFFSET);\n}\n\nvoid CHyprBorderDecoration::draw(PHLMONITOR pMonitor, float const& a) {\n    if (doesntWantBorders())\n        return;\n\n    if (m_assignedGeometry.width < m_extents.topLeft.x + 1 || m_assignedGeometry.height < m_extents.topLeft.y + 1)\n        return;\n\n    CBox windowBox = assignedBoxGlobal().translate(-pMonitor->m_position + m_window->m_floatingOffset).expand(-m_window->getRealBorderSize()).scale(pMonitor->m_scale).round();\n\n    if (windowBox.width < 1 || windowBox.height < 1)\n        return;\n\n    auto       grad     = m_window->m_realBorderColor;\n    const bool ANIMATED = m_window->m_borderFadeAnimationProgress->isBeingAnimated();\n\n    if (m_window->m_borderAngleAnimationProgress->enabled()) {\n        grad.m_angle += m_window->m_borderAngleAnimationProgress->value() * M_PI * 2;\n        grad.m_angle = normalizeAngleRad(grad.m_angle);\n\n        // When borderangle is animated, it is counterintuitive to fade between inactive/active gradient angles.\n        // Instead we sync the angles to avoid fading between them and additionally rotating the border angle.\n        if (ANIMATED)\n            m_window->m_realBorderColorPrevious.m_angle = grad.m_angle;\n    }\n\n    int                             borderSize       = m_window->getRealBorderSize();\n    const auto                      ROUNDINGBASE     = m_window->rounding();\n    const auto                      ROUNDING         = ROUNDINGBASE * pMonitor->m_scale;\n    const auto                      ROUNDINGPOWER    = m_window->roundingPower();\n    const auto                      CORRECTIONOFFSET = (borderSize * (M_SQRT2 - 1) * std::max(2.0 - ROUNDINGPOWER, 0.0));\n    const auto                      OUTERROUND       = ((ROUNDINGBASE + borderSize) - CORRECTIONOFFSET) * pMonitor->m_scale;\n\n    CBorderPassElement::SBorderData data;\n    data.box           = windowBox;\n    data.grad1         = grad;\n    data.round         = ROUNDING;\n    data.outerRound    = OUTERROUND;\n    data.roundingPower = ROUNDINGPOWER;\n    data.a             = a;\n    data.borderSize    = borderSize;\n    data.window        = m_window;\n\n    if (ANIMATED) {\n        data.hasGrad2 = true;\n        data.grad1    = m_window->m_realBorderColorPrevious;\n        data.grad2    = grad;\n        data.lerp     = m_window->m_borderFadeAnimationProgress->value();\n    }\n\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CBorderPassElement>(data));\n}\n\neDecorationType CHyprBorderDecoration::getDecorationType() {\n    return DECORATION_BORDER;\n}\n\nvoid CHyprBorderDecoration::updateWindow(PHLWINDOW) {\n    auto borderSize = m_window->getRealBorderSize();\n\n    if (borderSize == m_lastBorderSize)\n        return;\n\n    if (borderSize <= 0 && m_lastBorderSize <= 0)\n        return;\n\n    m_lastBorderSize = borderSize;\n\n    g_pDecorationPositioner->repositionDeco(this);\n}\n\nvoid CHyprBorderDecoration::damageEntire() {\n    if (!validMapped(m_window) || m_window->m_fullscreenState.internal == FSMODE_FULLSCREEN)\n        return;\n\n    const auto GLOBAL_BOX = assignedBoxGlobal();\n    const auto ROUNDING   = m_window->rounding();\n    const auto BORDERSIZE = m_window->getRealBorderSize() + 1;\n\n    CRegion    borderRegion(GLOBAL_BOX);\n    borderRegion.subtract(GLOBAL_BOX.copy().expand(-(BORDERSIZE + ROUNDING)));\n    borderRegion.expand(2); // pad\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (!g_pHyprRenderer->shouldRenderWindow(m_window.lock(), m)) {\n            const CRegion monitorRegion({m->m_position, m->m_size});\n            borderRegion.subtract(monitorRegion);\n        }\n    }\n\n    g_pHyprRenderer->damageRegion(borderRegion);\n}\n\neDecorationLayer CHyprBorderDecoration::getDecorationLayer() {\n    return DECORATION_LAYER_OVER;\n}\n\nuint64_t CHyprBorderDecoration::getDecorationFlags() {\n    static auto PPARTOFWINDOW = CConfigValue<Hyprlang::INT>(\"decoration:border_part_of_window\");\n\n    return *PPARTOFWINDOW && !doesntWantBorders() ? DECORATION_PART_OF_MAIN_WINDOW : 0;\n}\n\nstd::string CHyprBorderDecoration::getDisplayName() {\n    return \"Border\";\n}\n\nbool CHyprBorderDecoration::doesntWantBorders() {\n    return m_window->m_X11DoesntWantBorders || m_window->getRealBorderSize() == 0 || !m_window->m_ruleApplicator->decorate().valueOrDefault();\n}\n"
  },
  {
    "path": "src/render/decorations/CHyprBorderDecoration.hpp",
    "content": "#pragma once\n\n#include \"IHyprWindowDecoration.hpp\"\n\nclass CHyprBorderDecoration : public IHyprWindowDecoration {\n  public:\n    CHyprBorderDecoration(PHLWINDOW);\n    virtual ~CHyprBorderDecoration() = default;\n\n    virtual SDecorationPositioningInfo getPositioningInfo();\n\n    virtual void                       onPositioningReply(const SDecorationPositioningReply& reply);\n\n    virtual void                       draw(PHLMONITOR, float const& a);\n\n    virtual eDecorationType            getDecorationType();\n\n    virtual void                       updateWindow(PHLWINDOW);\n\n    virtual void                       damageEntire();\n\n    virtual eDecorationLayer           getDecorationLayer();\n\n    virtual uint64_t                   getDecorationFlags();\n\n    virtual std::string                getDisplayName();\n\n  private:\n    SBoxExtents  m_extents;\n    SBoxExtents  m_reportedExtents;\n\n    PHLWINDOWREF m_window;\n\n    CBox         m_assignedGeometry = {0};\n\n    int          m_lastBorderSize = -1;\n\n    CBox         assignedBoxGlobal();\n    bool         doesntWantBorders();\n};\n"
  },
  {
    "path": "src/render/decorations/CHyprDropShadowDecoration.cpp",
    "content": "#include \"CHyprDropShadowDecoration.hpp\"\n\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../pass/ShadowPassElement.hpp\"\n#include \"../Renderer.hpp\"\n#include \"../pass/TextureMatteElement.hpp\"\n\nCHyprDropShadowDecoration::CHyprDropShadowDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) {\n    ;\n}\n\neDecorationType CHyprDropShadowDecoration::getDecorationType() {\n    return DECORATION_SHADOW;\n}\n\nSDecorationPositioningInfo CHyprDropShadowDecoration::getPositioningInfo() {\n    SDecorationPositioningInfo info;\n    info.policy         = DECORATION_POSITION_ABSOLUTE;\n    info.desiredExtents = m_extents;\n    info.edges          = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP;\n\n    m_reportedExtents = m_extents;\n    return info;\n}\n\nvoid CHyprDropShadowDecoration::onPositioningReply(const SDecorationPositioningReply& reply) {\n    updateWindow(m_window.lock());\n}\n\nuint64_t CHyprDropShadowDecoration::getDecorationFlags() {\n    return DECORATION_NON_SOLID;\n}\n\nstd::string CHyprDropShadowDecoration::getDisplayName() {\n    return \"Drop Shadow\";\n}\n\nvoid CHyprDropShadowDecoration::damageEntire() {\n    static auto PSHADOWS = CConfigValue<Hyprlang::INT>(\"decoration:shadow:enabled\");\n\n    if (*PSHADOWS != 1)\n        return; // disabled\n\n    const auto PWINDOW = m_window.lock();\n    const auto pos     = PWINDOW->m_realPosition->value();\n    const auto size    = PWINDOW->m_realSize->value();\n\n    CBox       shadowBox = {pos.x - m_extents.topLeft.x, pos.y - m_extents.topLeft.y, pos.x + size.x + m_extents.bottomRight.x, pos.y + size.y + m_extents.bottomRight.y};\n\n    const auto PWORKSPACE  = PWINDOW->m_workspace;\n    const auto applyOffset = [&](CBox& b) {\n        if (PWORKSPACE && PWORKSPACE->m_renderOffset->isBeingAnimated() && !PWINDOW->m_pinned)\n            b.translate(PWORKSPACE->m_renderOffset->value());\n        b.translate(PWINDOW->m_floatingOffset);\n    };\n\n    applyOffset(shadowBox);\n\n    static auto PSHADOWIGNOREWINDOW = CConfigValue<Hyprlang::INT>(\"decoration:shadow:ignore_window\");\n    const auto  ROUNDING            = PWINDOW->rounding();\n    const auto  ROUNDINGSIZE        = ROUNDING - M_SQRT1_2 * ROUNDING + 1;\n\n    CRegion     shadowRegion(shadowBox);\n    if (*PSHADOWIGNOREWINDOW) {\n        CBox surfaceBox = PWINDOW->getWindowMainSurfaceBox();\n        applyOffset(surfaceBox);\n        surfaceBox.expand(-ROUNDINGSIZE);\n        shadowRegion.subtract(CRegion(surfaceBox));\n    }\n\n    for (auto const& m : g_pCompositor->m_monitors) {\n        if (!g_pHyprRenderer->shouldRenderWindow(PWINDOW, m)) {\n            const CRegion monitorRegion({m->m_position, m->m_size});\n            shadowRegion.subtract(monitorRegion);\n        }\n    }\n\n    g_pHyprRenderer->damageRegion(shadowRegion);\n}\n\nvoid CHyprDropShadowDecoration::updateWindow(PHLWINDOW pWindow) {\n    const auto PWINDOW = m_window.lock();\n\n    m_lastWindowPos  = PWINDOW->m_realPosition->value();\n    m_lastWindowSize = PWINDOW->m_realSize->value();\n\n    m_lastWindowBox          = {m_lastWindowPos.x, m_lastWindowPos.y, m_lastWindowSize.x, m_lastWindowSize.y};\n    m_lastWindowBoxWithDecos = g_pDecorationPositioner->getBoxWithIncludedDecos(pWindow);\n}\n\nvoid CHyprDropShadowDecoration::draw(PHLMONITOR pMonitor, float const& a) {\n    CShadowPassElement::SShadowData data;\n    data.deco = this;\n    data.a    = a;\n    g_pHyprRenderer->m_renderPass.add(makeUnique<CShadowPassElement>(data));\n}\n\nbool CHyprDropShadowDecoration::canRender(PHLMONITOR pMonitor) {\n    static auto PSHADOWS = CConfigValue<Hyprlang::INT>(\"decoration:shadow:enabled\");\n    if (*PSHADOWS != 1)\n        return false; // disabled\n\n    const auto PWINDOW = m_window.lock();\n\n    if (!validMapped(PWINDOW))\n        return false;\n\n    if (PWINDOW->m_realShadowColor->value() == CHyprColor(0, 0, 0, 0))\n        return false; // don't draw invisible shadows\n\n    if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault())\n        return false;\n\n    if (PWINDOW->m_ruleApplicator->noShadow().valueOrDefault())\n        return false;\n\n    return true;\n}\n\nSShadowRenderData CHyprDropShadowDecoration::getRenderData(PHLMONITOR pMonitor, float const& a) {\n    if (!canRender(pMonitor))\n        return {};\n\n    const auto  PWINDOW = m_window.lock();\n\n    static auto PSHADOWSIZE         = CConfigValue<Hyprlang::INT>(\"decoration:shadow:range\");\n    static auto PSHADOWIGNOREWINDOW = CConfigValue<Hyprlang::INT>(\"decoration:shadow:ignore_window\");\n    static auto PSHADOWSCALE        = CConfigValue<Hyprlang::FLOAT>(\"decoration:shadow:scale\");\n    static auto PSHADOWOFFSET       = CConfigValue<Hyprlang::VEC2>(\"decoration:shadow:offset\");\n\n    const auto  BORDERSIZE       = PWINDOW->getRealBorderSize();\n    const auto  ROUNDINGBASE     = PWINDOW->rounding();\n    const auto  ROUNDINGPOWER    = PWINDOW->roundingPower();\n    const auto  CORRECTIONOFFSET = (BORDERSIZE * (M_SQRT2 - 1) * std::max(2.0 - ROUNDINGPOWER, 0.0));\n    const auto  ROUNDING         = ROUNDINGBASE > 0 ? (ROUNDINGBASE + BORDERSIZE) - CORRECTIONOFFSET : 0;\n    const auto  PWORKSPACE       = PWINDOW->m_workspace;\n    const auto  WORKSPACEOFFSET  = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D();\n\n    // draw the shadow\n    CBox fullBox = m_lastWindowBoxWithDecos;\n    fullBox.translate(-pMonitor->m_position + WORKSPACEOFFSET);\n    fullBox.x -= *PSHADOWSIZE;\n    fullBox.y -= *PSHADOWSIZE;\n    fullBox.w += 2 * *PSHADOWSIZE;\n    fullBox.h += 2 * *PSHADOWSIZE;\n\n    const float SHADOWSCALE = std::clamp(*PSHADOWSCALE, 0.f, 1.f);\n\n    // scale the box in relation to the center of the box\n    fullBox.scaleFromCenter(SHADOWSCALE).translate({(*PSHADOWOFFSET).x, (*PSHADOWOFFSET).y});\n\n    updateWindow(PWINDOW);\n    m_lastWindowPos += WORKSPACEOFFSET;\n    m_extents = {\n        .topLeft =\n            {\n                m_lastWindowPos.x - fullBox.x - pMonitor->m_position.x + 2,\n                m_lastWindowPos.y - fullBox.y - pMonitor->m_position.y + 2,\n            },\n        .bottomRight =\n            {\n                fullBox.x + fullBox.width + pMonitor->m_position.x - m_lastWindowPos.x - m_lastWindowSize.x + 2,\n                fullBox.y + fullBox.height + pMonitor->m_position.y - m_lastWindowPos.y - m_lastWindowSize.y + 2,\n            },\n    };\n\n    fullBox.translate(PWINDOW->m_floatingOffset);\n\n    if (fullBox.width < 1 || fullBox.height < 1)\n        return {}; // don't draw invisible shadows\n\n    g_pHyprRenderer->m_renderData.currentWindow = m_window;\n\n    CBox    windowBox;\n    CRegion saveDamage;\n    fullBox.scale(pMonitor->m_scale).round();\n    if (*PSHADOWIGNOREWINDOW) {\n        windowBox      = m_lastWindowBox;\n        CBox withDecos = m_lastWindowBoxWithDecos;\n\n        // get window box\n        windowBox.translate(-pMonitor->m_position + WORKSPACEOFFSET);\n        withDecos.translate(-pMonitor->m_position + WORKSPACEOFFSET);\n\n        windowBox.translate(PWINDOW->m_floatingOffset);\n        withDecos.translate(PWINDOW->m_floatingOffset);\n\n        auto scaledExtentss = withDecos.extentsFrom(windowBox);\n        scaledExtentss      = scaledExtentss * pMonitor->m_scale;\n        scaledExtentss      = scaledExtentss.round();\n\n        // add extents\n        windowBox.scale(pMonitor->m_scale).round().addExtents(scaledExtentss);\n\n        if (windowBox.width < 1 || windowBox.height < 1)\n            return {}; // prevent assert failed\n\n        saveDamage = g_pHyprRenderer->m_renderData.damage;\n\n        g_pHyprRenderer->m_renderData.damage = fullBox;\n        g_pHyprRenderer->m_renderData.damage.subtract(windowBox.copy().expand(-ROUNDING * pMonitor->m_scale)).intersect(saveDamage);\n        g_pHyprRenderer->m_renderData.renderModif.applyToRegion(g_pHyprRenderer->m_renderData.damage);\n    }\n\n    return {\n        .ignoreWindow  = *PSHADOWIGNOREWINDOW,\n        .valid         = true,\n        .fullBox       = fullBox,\n        .windowBox     = windowBox,\n        .saveDamage    = saveDamage,\n        .rounding      = ROUNDING,\n        .roundingPower = ROUNDINGPOWER,\n        .size          = *PSHADOWSIZE,\n    };\n}\n\nvoid CHyprDropShadowDecoration::reposition() {\n    if (m_extents != m_reportedExtents)\n        g_pDecorationPositioner->repositionDeco(this);\n\n    g_pHyprRenderer->m_renderData.currentWindow.reset();\n}\n\n// TODO remove\nvoid CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) {\n    auto data = getRenderData(pMonitor, a);\n    if (!data.valid)\n        return;\n\n    const auto PWINDOW = m_window.lock();\n\n    g_pHyprRenderer->disableScissor();\n\n    if (data.ignoreWindow) {\n        // we'll take the liberty of using this as it should not be used rn\n        const auto alphaFB     = g_pHyprRenderer->m_renderData.pMonitor->m_mirrorFB;\n        const auto alphaSwapFB = g_pHyprRenderer->m_renderData.pMonitor->m_mirrorSwapFB;\n        const auto LASTFB      = g_pHyprRenderer->m_renderData.currentFB;\n\n        CBox       monbox = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};\n\n        alphaFB->bind();\n\n        // build the matte\n        // 10-bit formats have dogshit alpha channels, so we have to use the matte to its fullest.\n        // first, clear region of interest with black (fully transparent)\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{.box = data.fullBox, .color = CHyprColor(0, 0, 0, 1), .round = 0}), monbox);\n\n        // render white shadow with the alpha of the shadow color (otherwise we clear with alpha later and shit it to 2 bit)\n        drawShadowInternal(data.fullBox, data.rounding * pMonitor->m_scale, data.roundingPower, data.size * pMonitor->m_scale,\n                           CHyprColor(1, 1, 1, PWINDOW->m_realShadowColor->value().a), a);\n\n        // render black window box (\"clip\")\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{\n                                  .box           = data.windowBox,\n                                  .color         = CHyprColor(0, 0, 0, 1),\n                                  .round         = (data.rounding + 1 /* This fixes small pixel gaps. */) * pMonitor->m_scale,\n                                  .roundingPower = data.roundingPower,\n                              }),\n                              monbox);\n\n        alphaSwapFB->bind();\n\n        // alpha swap just has the shadow color. It will be the \"texture\" to render.\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{.box = data.fullBox, .color = PWINDOW->m_realShadowColor->value().stripA(), .round = 0}),\n                              monbox);\n\n        LASTFB->bind();\n\n        g_pHyprRenderer->pushMonitorTransformEnabled(true);\n        g_pHyprRenderer->m_renderData.renderModif.enabled = false;\n\n        g_pHyprRenderer->draw(makeUnique<CTextureMatteElement>(CTextureMatteElement::STextureMatteData{\n                                  .box = monbox,\n                                  .tex = alphaSwapFB->getTexture(),\n                                  .fb  = alphaFB,\n                              }),\n                              {});\n\n        g_pHyprRenderer->m_renderData.renderModif.enabled = true;\n        g_pHyprRenderer->popMonitorTransformEnabled();\n\n        g_pHyprRenderer->m_renderData.damage = data.saveDamage;\n    } else\n        drawShadowInternal(data.fullBox, data.rounding * pMonitor->m_scale, data.roundingPower, data.size * pMonitor->m_scale, PWINDOW->m_realShadowColor->value(), a);\n\n    reposition();\n}\n\neDecorationLayer CHyprDropShadowDecoration::getDecorationLayer() {\n    return DECORATION_LAYER_BOTTOM;\n}\n\nvoid CHyprDropShadowDecoration::drawShadowInternal(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) {\n    static auto PSHADOWSHARP = CConfigValue<Hyprlang::INT>(\"decoration:shadow:sharp\");\n\n    if (box.w < 1 || box.h < 1)\n        return;\n\n    g_pHyprRenderer->blend(true);\n\n    color.a *= a;\n\n    if (*PSHADOWSHARP)\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(CRectPassElement::SRectData{\n                                  .box           = box,\n                                  .color         = color,\n                                  .round         = round,\n                                  .roundingPower = roundingPower,\n                              }),\n                              {});\n    else\n        g_pHyprRenderer->drawShadow(box, round, roundingPower, range, color, 1.F);\n}\n"
  },
  {
    "path": "src/render/decorations/CHyprDropShadowDecoration.hpp",
    "content": "#pragma once\n\n#include \"IHyprWindowDecoration.hpp\"\n\nstruct SShadowRenderData {\n    bool    ignoreWindow = false;\n    bool    valid        = false;\n    CBox    fullBox;\n    CBox    windowBox;\n    CRegion saveDamage;\n    float   rounding      = 0;\n    float   roundingPower = 0;\n    int     size          = 0;\n};\n\nclass CHyprDropShadowDecoration : public IHyprWindowDecoration {\n  public:\n    CHyprDropShadowDecoration(PHLWINDOW);\n    virtual ~CHyprDropShadowDecoration() = default;\n\n    virtual SDecorationPositioningInfo getPositioningInfo();\n\n    virtual void                       onPositioningReply(const SDecorationPositioningReply& reply);\n\n    virtual void                       draw(PHLMONITOR, float const& a);\n\n    virtual eDecorationType            getDecorationType();\n\n    virtual void                       updateWindow(PHLWINDOW);\n\n    virtual void                       damageEntire();\n\n    virtual eDecorationLayer           getDecorationLayer();\n\n    virtual uint64_t                   getDecorationFlags();\n\n    virtual std::string                getDisplayName();\n\n    bool                               canRender(PHLMONITOR);\n    SShadowRenderData                  getRenderData(PHLMONITOR, float const& a);\n    void                               reposition();\n\n    // TODO remove\n    void render(PHLMONITOR, float const& a);\n\n  private:\n    SBoxExtents  m_extents;\n    SBoxExtents  m_reportedExtents;\n\n    PHLWINDOWREF m_window;\n\n    Vector2D     m_lastWindowPos;\n    Vector2D     m_lastWindowSize;\n\n    void         drawShadowInternal(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a);\n\n    CBox         m_lastWindowBox          = {0};\n    CBox         m_lastWindowBoxWithDecos = {0};\n};\n"
  },
  {
    "path": "src/render/decorations/CHyprGroupBarDecoration.cpp",
    "content": "#include \"CHyprGroupBarDecoration.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../desktop/view/Group.hpp\"\n#include <ranges>\n#include <pango/pangocairo.h>\n#include \"../pass/TexPassElement.hpp\"\n#include \"../pass/RectPassElement.hpp\"\n#include \"../Renderer.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../layout/LayoutManager.hpp\"\n#include \"../../layout/supplementary/DragController.hpp\"\n\n// shared things to conserve VRAM\nstatic SP<ITexture> m_tGradientActive;\nstatic SP<ITexture> m_tGradientInactive;\nstatic SP<ITexture> m_tGradientLockedActive;\nstatic SP<ITexture> m_tGradientLockedInactive;\n\nconstexpr int       BAR_TEXT_PAD = 2;\n\nCHyprGroupBarDecoration::CHyprGroupBarDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) {\n    static auto PGRADIENTS = CConfigValue<Hyprlang::INT>(\"group:groupbar:enabled\");\n    static auto PENABLED   = CConfigValue<Hyprlang::INT>(\"group:groupbar:gradients\");\n\n    if (!m_tGradientActive)\n        m_tGradientActive = g_pHyprRenderer->createTexture();\n    if (!m_tGradientInactive)\n        m_tGradientInactive = g_pHyprRenderer->createTexture();\n    if (!m_tGradientLockedActive)\n        m_tGradientLockedActive = g_pHyprRenderer->createTexture();\n    if (!m_tGradientLockedInactive)\n        m_tGradientLockedInactive = g_pHyprRenderer->createTexture();\n\n    if (!m_tGradientActive->ok() && *PENABLED && *PGRADIENTS)\n        refreshGroupBarGradients();\n}\n\nSDecorationPositioningInfo CHyprGroupBarDecoration::getPositioningInfo() {\n    static auto                PHEIGHT          = CConfigValue<Hyprlang::INT>(\"group:groupbar:height\");\n    static auto                PINDICATORGAP    = CConfigValue<Hyprlang::INT>(\"group:groupbar:indicator_gap\");\n    static auto                PINDICATORHEIGHT = CConfigValue<Hyprlang::INT>(\"group:groupbar:indicator_height\");\n    static auto                PRENDERTITLES    = CConfigValue<Hyprlang::INT>(\"group:groupbar:render_titles\");\n    static auto                PGRADIENTS       = CConfigValue<Hyprlang::INT>(\"group:groupbar:gradients\");\n    static auto                PPRIORITY        = CConfigValue<Hyprlang::INT>(\"group:groupbar:priority\");\n    static auto                PSTACKED         = CConfigValue<Hyprlang::INT>(\"group:groupbar:stacked\");\n    static auto                POUTERGAP        = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_out\");\n    static auto                PKEEPUPPERGAP    = CConfigValue<Hyprlang::INT>(\"group:groupbar:keep_upper_gap\");\n\n    SDecorationPositioningInfo info;\n    info.policy   = DECORATION_POSITION_STICKY;\n    info.edges    = DECORATION_EDGE_TOP;\n    info.priority = *PPRIORITY;\n    info.reserved = true;\n\n    if (visible()) {\n        if (*PSTACKED) {\n            const auto ONEBARHEIGHT = *POUTERGAP + *PINDICATORHEIGHT + *PINDICATORGAP + (*PGRADIENTS || *PRENDERTITLES ? *PHEIGHT : 0);\n            info.desiredExtents     = {{0, (ONEBARHEIGHT * m_dwGroupMembers.size()) + (*PKEEPUPPERGAP * *POUTERGAP)}, {0, 0}};\n        } else\n            info.desiredExtents = {{0, *POUTERGAP * (1 + *PKEEPUPPERGAP) + *PINDICATORHEIGHT + *PINDICATORGAP + (*PGRADIENTS || *PRENDERTITLES ? *PHEIGHT : 0)}, {0, 0}};\n    } else\n        info.desiredExtents = {{0, 0}, {0, 0}};\n    return info;\n}\n\nvoid CHyprGroupBarDecoration::onPositioningReply(const SDecorationPositioningReply& reply) {\n    m_assignedBox = reply.assignedGeometry;\n}\n\neDecorationType CHyprGroupBarDecoration::getDecorationType() {\n    return DECORATION_GROUPBAR;\n}\n\n//\n\nvoid CHyprGroupBarDecoration::updateWindow(PHLWINDOW pWindow) {\n    if (!m_window->m_group) {\n        m_window->removeWindowDeco(this);\n        return;\n    }\n\n    m_dwGroupMembers.clear();\n    for (const auto& w : m_window->m_group->windows()) {\n        m_dwGroupMembers.emplace_back(w);\n    }\n\n    damageEntire();\n\n    if (m_dwGroupMembers.empty()) {\n        m_window->removeWindowDeco(this);\n        return;\n    }\n}\n\nvoid CHyprGroupBarDecoration::damageEntire() {\n    auto box = assignedBoxGlobal();\n    box.translate(m_window->m_floatingOffset);\n    g_pHyprRenderer->damageBox(box);\n}\n\nvoid CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) {\n    // get how many bars we will draw\n    int        barsToDraw = m_dwGroupMembers.size();\n\n    const bool VISIBLE = visible();\n\n    if (VISIBLE != m_bLastVisibilityStatus)\n        g_pDecorationPositioner->repositionDeco(this);\n\n    if (!VISIBLE)\n        return;\n\n    static auto PRENDERTITLES              = CConfigValue<Hyprlang::INT>(\"group:groupbar:render_titles\");\n    static auto PTITLEFONTSIZE             = CConfigValue<Hyprlang::INT>(\"group:groupbar:font_size\");\n    static auto PHEIGHT                    = CConfigValue<Hyprlang::INT>(\"group:groupbar:height\");\n    static auto PINDICATORGAP              = CConfigValue<Hyprlang::INT>(\"group:groupbar:indicator_gap\");\n    static auto PINDICATORHEIGHT           = CConfigValue<Hyprlang::INT>(\"group:groupbar:indicator_height\");\n    static auto PGRADIENTS                 = CConfigValue<Hyprlang::INT>(\"group:groupbar:gradients\");\n    static auto PSTACKED                   = CConfigValue<Hyprlang::INT>(\"group:groupbar:stacked\");\n    static auto PROUNDING                  = CConfigValue<Hyprlang::INT>(\"group:groupbar:rounding\");\n    static auto PROUNDINGPOWER             = CConfigValue<Hyprlang::FLOAT>(\"group:groupbar:rounding_power\");\n    static auto PGRADIENTROUNDING          = CConfigValue<Hyprlang::INT>(\"group:groupbar:gradient_rounding\");\n    static auto PGRADIENTROUNDINGPOWER     = CConfigValue<Hyprlang::FLOAT>(\"group:groupbar:gradient_rounding_power\");\n    static auto PGRADIENTROUNDINGONLYEDGES = CConfigValue<Hyprlang::INT>(\"group:groupbar:gradient_round_only_edges\");\n    static auto PROUNDONLYEDGES            = CConfigValue<Hyprlang::INT>(\"group:groupbar:round_only_edges\");\n    static auto PGROUPCOLACTIVE            = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.active\");\n    static auto PGROUPCOLINACTIVE          = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.inactive\");\n    static auto PGROUPCOLACTIVELOCKED      = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.locked_active\");\n    static auto PGROUPCOLINACTIVELOCKED    = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.locked_inactive\");\n    static auto POUTERGAP                  = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_out\");\n    static auto PINNERGAP                  = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_in\");\n    static auto PKEEPUPPERGAP              = CConfigValue<Hyprlang::INT>(\"group:groupbar:keep_upper_gap\");\n    static auto PTEXTOFFSET                = CConfigValue<Hyprlang::INT>(\"group:groupbar:text_offset\");\n    static auto PTEXTPADDING               = CConfigValue<Hyprlang::INT>(\"group:groupbar:text_padding\");\n    static auto PBLUR                      = CConfigValue<Hyprlang::INT>(\"group:groupbar:blur\");\n    auto* const GROUPCOLACTIVE             = sc<CGradientValueData*>((PGROUPCOLACTIVE.ptr())->getData());\n    auto* const GROUPCOLINACTIVE           = sc<CGradientValueData*>((PGROUPCOLINACTIVE.ptr())->getData());\n    auto* const GROUPCOLACTIVELOCKED       = sc<CGradientValueData*>((PGROUPCOLACTIVELOCKED.ptr())->getData());\n    auto* const GROUPCOLINACTIVELOCKED     = sc<CGradientValueData*>((PGROUPCOLINACTIVELOCKED.ptr())->getData());\n\n    const auto  ASSIGNEDBOX = assignedBoxGlobal();\n\n    const auto  ONEBARHEIGHT = *POUTERGAP + *PINDICATORHEIGHT + *PINDICATORGAP + (*PGRADIENTS || *PRENDERTITLES ? *PHEIGHT : 0);\n    m_barWidth               = *PSTACKED ? ASSIGNEDBOX.w : (ASSIGNEDBOX.w - *PINNERGAP * (barsToDraw - 1)) / barsToDraw;\n    m_barHeight              = *PSTACKED ? ((ASSIGNEDBOX.h - *POUTERGAP * *PKEEPUPPERGAP) - *POUTERGAP * (barsToDraw)) / barsToDraw : ASSIGNEDBOX.h - *POUTERGAP * *PKEEPUPPERGAP;\n\n    const auto DESIREDHEIGHT = *PSTACKED ? (ONEBARHEIGHT * m_dwGroupMembers.size()) + *POUTERGAP * *PKEEPUPPERGAP : *POUTERGAP * (1 + *PKEEPUPPERGAP) + ONEBARHEIGHT;\n    if (DESIREDHEIGHT != ASSIGNEDBOX.h)\n        g_pDecorationPositioner->repositionDeco(this);\n\n    float xoff = 0;\n    float yoff = 0;\n\n    bool  blur = *PBLUR != 0;\n\n    for (int i = 0; i < barsToDraw; ++i) {\n        const auto WINDOWINDEX = *PSTACKED ? m_dwGroupMembers.size() - i - 1 : i;\n\n        CBox       rect = {ASSIGNEDBOX.x + xoff - pMonitor->m_position.x + m_window->m_floatingOffset.x,\n                           ASSIGNEDBOX.y + ASSIGNEDBOX.h - floor(yoff) - *PINDICATORHEIGHT - *POUTERGAP - pMonitor->m_position.y + m_window->m_floatingOffset.y, m_barWidth,\n                           *PINDICATORHEIGHT};\n\n        rect.scale(pMonitor->m_scale).round();\n\n        const bool        GROUPLOCKED  = m_window->m_group->locked() || g_pKeybindManager->m_groupsLocked;\n        const auto* const PCOLACTIVE   = GROUPLOCKED ? GROUPCOLACTIVELOCKED : GROUPCOLACTIVE;\n        const auto* const PCOLINACTIVE = GROUPLOCKED ? GROUPCOLINACTIVELOCKED : GROUPCOLINACTIVE;\n\n        CHyprColor        color = m_dwGroupMembers[WINDOWINDEX].lock() == Desktop::focusState()->window() ? PCOLACTIVE->m_colors[0] : PCOLINACTIVE->m_colors[0];\n        color.a *= a;\n\n        if (!rect.empty()) {\n            CRectPassElement::SRectData rectdata;\n            rectdata.color = color;\n            rectdata.blur  = blur;\n            rectdata.box   = rect;\n            if (*PROUNDING) {\n                rectdata.round         = *PROUNDING;\n                rectdata.roundingPower = *PROUNDINGPOWER;\n                if (*PROUNDONLYEDGES) {\n                    rectdata.round      = 0;\n                    const double offset = *PROUNDING * 2;\n                    if (i == 0) {\n                        rectdata.round   = *PROUNDING;\n                        rectdata.clipBox = rect;\n                        rectdata.box     = CBox{rect.pos(), Vector2D{rect.w + offset, rect.h}};\n                    } else if (i == barsToDraw - 1) {\n                        rectdata.round   = *PROUNDING;\n                        rectdata.clipBox = rect;\n                        rectdata.box     = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}};\n                    }\n                }\n            }\n            g_pHyprRenderer->m_renderPass.add(makeUnique<CRectPassElement>(rectdata));\n        }\n\n        rect = {ASSIGNEDBOX.x + xoff - pMonitor->m_position.x + m_window->m_floatingOffset.x,\n                ASSIGNEDBOX.y + ASSIGNEDBOX.h - floor(yoff) - ONEBARHEIGHT - pMonitor->m_position.y + m_window->m_floatingOffset.y, m_barWidth,\n                (*PGRADIENTS || *PRENDERTITLES ? *PHEIGHT : 0)};\n        rect.scale(pMonitor->m_scale);\n\n        if (!rect.empty()) {\n            if (*PGRADIENTS) {\n                const auto GRADIENTTEX = (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window() ? (GROUPLOCKED ? m_tGradientLockedActive : m_tGradientActive) :\n                                                                                                             (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive));\n                if (GRADIENTTEX->ok()) {\n                    CTexPassElement::SRenderData data;\n                    data.tex  = GRADIENTTEX;\n                    data.blur = blur;\n                    data.box  = rect;\n                    data.a    = a;\n                    if (*PGRADIENTROUNDING) {\n                        data.round         = *PGRADIENTROUNDING;\n                        data.roundingPower = *PGRADIENTROUNDINGPOWER;\n                        if (*PGRADIENTROUNDINGONLYEDGES) {\n                            data.round          = 0;\n                            const double offset = *PGRADIENTROUNDING * 2;\n                            if (i == 0) {\n                                data.round   = *PGRADIENTROUNDING;\n                                data.clipBox = rect;\n                                data.box     = CBox{rect.pos(), Vector2D{rect.w + offset, rect.h}};\n                            } else if (i == barsToDraw - 1) {\n                                data.round   = *PGRADIENTROUNDING;\n                                data.clipBox = rect;\n                                data.box     = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}};\n                            }\n                        }\n                    }\n                    g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(data));\n                }\n            }\n\n            if (*PRENDERTITLES) {\n                CTitleTex* pTitleTex = textureFromTitle(m_dwGroupMembers[WINDOWINDEX]->m_title);\n\n                if (!pTitleTex)\n                    pTitleTex =\n                        m_titleTexs.titleTexs\n                            .emplace_back(makeUnique<CTitleTex>(\n                                m_dwGroupMembers[WINDOWINDEX].lock(),\n                                Vector2D{(m_barWidth - (*PTEXTPADDING * 2)) * pMonitor->m_scale, (*PTITLEFONTSIZE + 2L * BAR_TEXT_PAD) * pMonitor->m_scale}, pMonitor->m_scale))\n                            .get();\n\n                SP<ITexture> titleTex;\n                if (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window())\n                    titleTex = GROUPLOCKED ? pTitleTex->m_texLockedActive : pTitleTex->m_texActive;\n                else\n                    titleTex = GROUPLOCKED ? pTitleTex->m_texLockedInactive : pTitleTex->m_texInactive;\n\n                rect.y += std::ceil(((rect.height - titleTex->m_size.y) / 2.0) - (*PTEXTOFFSET * pMonitor->m_scale));\n                rect.height = titleTex->m_size.y;\n                rect.width  = titleTex->m_size.x;\n                rect.x += std::round((((m_barWidth + *PTEXTPADDING) * pMonitor->m_scale) / 2.0) - ((titleTex->m_size.x + *PTEXTPADDING) / 2.0));\n                rect.round();\n\n                CTexPassElement::SRenderData data;\n                data.tex = titleTex;\n                data.box = rect;\n                data.a   = a;\n                g_pHyprRenderer->m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));\n            }\n        }\n\n        if (*PSTACKED)\n            yoff += ONEBARHEIGHT;\n        else\n            xoff += *PINNERGAP + m_barWidth;\n    }\n\n    if (*PRENDERTITLES)\n        invalidateTextures();\n}\n\nCTitleTex* CHyprGroupBarDecoration::textureFromTitle(const std::string& title) {\n    for (auto const& tex : m_titleTexs.titleTexs) {\n        if (tex->m_content == title)\n            return tex.get();\n    }\n\n    return nullptr;\n}\n\nvoid CHyprGroupBarDecoration::invalidateTextures() {\n    m_titleTexs.titleTexs.clear();\n}\n\nCTitleTex::CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float monitorScale) : m_content(pWindow->m_title), m_windowOwner(pWindow) {\n    static auto      FALLBACKFONT             = CConfigValue<std::string>(\"misc:font_family\");\n    static auto      PTITLEFONTFAMILY         = CConfigValue<std::string>(\"group:groupbar:font_family\");\n    static auto      PTITLEFONTSIZE           = CConfigValue<Hyprlang::INT>(\"group:groupbar:font_size\");\n    static auto      PTEXTCOLORACTIVE         = CConfigValue<Hyprlang::INT>(\"group:groupbar:text_color\");\n    static auto      PTEXTCOLORINACTIVE       = CConfigValue<Hyprlang::INT>(\"group:groupbar:text_color_inactive\");\n    static auto      PTEXTCOLORLOCKEDACTIVE   = CConfigValue<Hyprlang::INT>(\"group:groupbar:text_color_locked_active\");\n    static auto      PTEXTCOLORLOCKEDINACTIVE = CConfigValue<Hyprlang::INT>(\"group:groupbar:text_color_locked_inactive\");\n\n    static auto      PTITLEFONTWEIGHTACTIVE   = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:font_weight_active\");\n    static auto      PTITLEFONTWEIGHTINACTIVE = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:font_weight_inactive\");\n\n    const auto       FONTWEIGHTACTIVE   = sc<CFontWeightConfigValueData*>((PTITLEFONTWEIGHTACTIVE.ptr())->getData());\n    const auto       FONTWEIGHTINACTIVE = sc<CFontWeightConfigValueData*>((PTITLEFONTWEIGHTINACTIVE.ptr())->getData());\n\n    const CHyprColor COLORACTIVE         = CHyprColor(*PTEXTCOLORACTIVE);\n    const CHyprColor COLORINACTIVE       = *PTEXTCOLORINACTIVE == -1 ? COLORACTIVE : CHyprColor(*PTEXTCOLORINACTIVE);\n    const CHyprColor COLORLOCKEDACTIVE   = *PTEXTCOLORLOCKEDACTIVE == -1 ? COLORACTIVE : CHyprColor(*PTEXTCOLORLOCKEDACTIVE);\n    const CHyprColor COLORLOCKEDINACTIVE = *PTEXTCOLORLOCKEDINACTIVE == -1 ? COLORINACTIVE : CHyprColor(*PTEXTCOLORLOCKEDINACTIVE);\n\n    const auto       FONTFAMILY = *PTITLEFONTFAMILY != STRVAL_EMPTY ? *PTITLEFONTFAMILY : *FALLBACKFONT;\n\n#define RENDER_TEXT(color, weight) g_pHyprRenderer->renderText(pWindow->m_title, (color), *PTITLEFONTSIZE* monitorScale, false, FONTFAMILY, bufferSize.x - 2, (weight));\n    m_texActive         = RENDER_TEXT(COLORACTIVE, FONTWEIGHTACTIVE->m_value);\n    m_texInactive       = RENDER_TEXT(COLORINACTIVE, FONTWEIGHTINACTIVE->m_value);\n    m_texLockedActive   = RENDER_TEXT(COLORLOCKEDACTIVE, FONTWEIGHTACTIVE->m_value);\n    m_texLockedInactive = RENDER_TEXT(COLORLOCKEDINACTIVE, FONTWEIGHTINACTIVE->m_value);\n#undef RENDER_TEXT\n}\n\nstatic void renderGradientTo(SP<ITexture> tex, CGradientValueData* grad) {\n\n    if (!Desktop::focusState()->monitor())\n        return;\n\n    const Vector2D& bufferSize = Desktop::focusState()->monitor()->m_pixelSize;\n\n    const auto      CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y);\n    const auto      CAIRO        = cairo_create(CAIROSURFACE);\n\n    // clear the pixmap\n    cairo_save(CAIRO);\n    cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);\n    cairo_paint(CAIRO);\n    cairo_restore(CAIRO);\n\n    cairo_pattern_t* pattern;\n    pattern = cairo_pattern_create_linear(0, 0, 0, bufferSize.y);\n\n    for (unsigned long i = 0; i < grad->m_colors.size(); i++) {\n        cairo_pattern_add_color_stop_rgba(pattern, 1 - sc<double>(i + 1) / (grad->m_colors.size() + 1), grad->m_colors[i].r, grad->m_colors[i].g, grad->m_colors[i].b,\n                                          grad->m_colors[i].a);\n    }\n\n    cairo_rectangle(CAIRO, 0, 0, bufferSize.x, bufferSize.y);\n    cairo_set_source(CAIRO, pattern);\n    cairo_fill(CAIRO);\n    cairo_pattern_destroy(pattern);\n\n    cairo_surface_flush(CAIROSURFACE);\n\n    // copy the data to an OpenGL texture we have\n    tex = g_pHyprRenderer->createTexture(CAIROSURFACE);\n\n    // delete cairo\n    cairo_destroy(CAIRO);\n    cairo_surface_destroy(CAIROSURFACE);\n}\n\nvoid refreshGroupBarGradients() {\n    static auto PGRADIENTS = CConfigValue<Hyprlang::INT>(\"group:groupbar:enabled\");\n    static auto PENABLED   = CConfigValue<Hyprlang::INT>(\"group:groupbar:gradients\");\n\n    static auto PGROUPCOLACTIVE         = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.active\");\n    static auto PGROUPCOLINACTIVE       = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.inactive\");\n    static auto PGROUPCOLACTIVELOCKED   = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.locked_active\");\n    static auto PGROUPCOLINACTIVELOCKED = CConfigValue<Hyprlang::CUSTOMTYPE>(\"group:groupbar:col.locked_inactive\");\n    auto* const GROUPCOLACTIVE          = sc<CGradientValueData*>((PGROUPCOLACTIVE.ptr())->getData());\n    auto* const GROUPCOLINACTIVE        = sc<CGradientValueData*>((PGROUPCOLINACTIVE.ptr())->getData());\n    auto* const GROUPCOLACTIVELOCKED    = sc<CGradientValueData*>((PGROUPCOLACTIVELOCKED.ptr())->getData());\n    auto* const GROUPCOLINACTIVELOCKED  = sc<CGradientValueData*>((PGROUPCOLINACTIVELOCKED.ptr())->getData());\n\n    if (m_tGradientActive && m_tGradientActive->ok()) {\n        m_tGradientActive.reset();\n        m_tGradientInactive.reset();\n        m_tGradientLockedActive.reset();\n        m_tGradientLockedInactive.reset();\n    }\n\n    if (!*PENABLED || !*PGRADIENTS)\n        return;\n\n    renderGradientTo(m_tGradientActive, GROUPCOLACTIVE);\n    renderGradientTo(m_tGradientInactive, GROUPCOLINACTIVE);\n    renderGradientTo(m_tGradientLockedActive, GROUPCOLACTIVELOCKED);\n    renderGradientTo(m_tGradientLockedInactive, GROUPCOLINACTIVELOCKED);\n}\n\nbool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) {\n    static auto PSTACKED  = CConfigValue<Hyprlang::INT>(\"group:groupbar:stacked\");\n    static auto POUTERGAP = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_out\");\n    static auto PINNERGAP = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_in\");\n    if (m_window->m_group->size() == 1)\n        return false;\n\n    const float BARRELATIVEX = pos.x - assignedBoxGlobal().x;\n    const float BARRELATIVEY = pos.y - assignedBoxGlobal().y;\n    const int   WINDOWINDEX  = *PSTACKED ? (BARRELATIVEY / (m_barHeight + *POUTERGAP)) : (BARRELATIVEX) / (m_barWidth + *PINNERGAP);\n\n    if (!*PSTACKED && (BARRELATIVEX - (m_barWidth + *PINNERGAP) * WINDOWINDEX > m_barWidth))\n        return false;\n\n    if (*PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP))\n        return false;\n\n    PHLWINDOW   pWindow = m_window->m_group->fromIndex(WINDOWINDEX);\n\n    const auto& GROUP = m_window->m_group;\n\n    // remove the window from the group\n    GROUP->remove(pWindow);\n\n    // start a move drag on it\n    g_layoutManager->dragController()->dragBegin(pWindow->layoutTarget(), MBIND_MOVE);\n\n    if (!g_pCompositor->isWindowActive(pWindow))\n        Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK);\n\n    return true;\n}\n\nbool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWINDOW pDraggedWindow) {\n    static auto PDRAGINTOGROUP                   = CConfigValue<Hyprlang::INT>(\"group:drag_into_group\");\n    static auto PMERGEFLOATEDINTOTILEDONGROUPBAR = CConfigValue<Hyprlang::INT>(\"group:merge_floated_into_tiled_on_groupbar\");\n    static auto PMERGEGROUPSONGROUPBAR           = CConfigValue<Hyprlang::INT>(\"group:merge_groups_on_groupbar\");\n    const bool  FLOATEDINTOTILED                 = !m_window->m_isFloating && !g_layoutManager->dragController()->draggingTiled();\n\n    if (!pDraggedWindow->canBeGroupedInto(m_window->m_group) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) ||\n        (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_group))\n        return false;\n\n    m_window->m_group->add(pDraggedWindow);\n\n    if (!pDraggedWindow->getDecorationByType(DECORATION_GROUPBAR))\n        pDraggedWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pDraggedWindow));\n\n    return true;\n}\n\nbool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPointer::SButtonEvent& e) {\n    static auto PSTACKED  = CConfigValue<Hyprlang::INT>(\"group:groupbar:stacked\");\n    static auto POUTERGAP = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_out\");\n    static auto PINNERGAP = CConfigValue<Hyprlang::INT>(\"group:groupbar:gaps_in\");\n    if (m_window->isEffectiveInternalFSMode(FSMODE_FULLSCREEN))\n        return true;\n\n    const float BARRELATIVEX = pos.x - assignedBoxGlobal().x;\n    const float BARRELATIVEY = pos.y - assignedBoxGlobal().y;\n    const int   WINDOWINDEX  = *PSTACKED ? (BARRELATIVEY / (m_barHeight + *POUTERGAP)) : (BARRELATIVEX) / (m_barWidth + *PINNERGAP);\n    static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>(\"input:follow_mouse\");\n\n    // close window on middle click\n    if (e.button == 274) {\n        static Vector2D pressedCursorPos;\n\n        if (e.state == WL_POINTER_BUTTON_STATE_PRESSED)\n            pressedCursorPos = pos;\n        else if (e.state == WL_POINTER_BUTTON_STATE_RELEASED && pressedCursorPos == pos)\n            g_pXWaylandManager->sendCloseWindow(m_window->m_group->fromIndex(WINDOWINDEX));\n\n        return true;\n    }\n\n    if (e.state != WL_POINTER_BUTTON_STATE_PRESSED)\n        return true;\n\n    // click on padding\n    const auto TABPAD   = !*PSTACKED && (BARRELATIVEX - (m_barWidth + *PINNERGAP) * WINDOWINDEX > m_barWidth);\n    const auto STACKPAD = *PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP);\n    if (TABPAD || STACKPAD) {\n        if (!g_pCompositor->isWindowActive(m_window.lock()))\n            Desktop::focusState()->rawWindowFocus(m_window.lock(), Desktop::FOCUS_REASON_CLICK);\n        return true;\n    }\n\n    PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX);\n\n    if (pWindow != m_window)\n        pWindow->m_group->setCurrent(pWindow);\n\n    if (!g_pCompositor->isWindowActive(pWindow) && *PFOLLOWMOUSE != 3)\n        Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK);\n\n    if (pWindow->m_isFloating)\n        g_pCompositor->changeWindowZOrder(pWindow, true);\n\n    return true;\n}\n\nbool CHyprGroupBarDecoration::onScrollOnDeco(const Vector2D& pos, const IPointer::SAxisEvent e) {\n    static auto PGROUPBARSCROLLING = CConfigValue<Hyprlang::INT>(\"group:groupbar:scrolling\");\n\n    if (!*PGROUPBARSCROLLING || !m_window->m_group)\n        return false;\n\n    if (e.delta > 0)\n        m_window->m_group->moveCurrent(true);\n    else\n        m_window->m_group->moveCurrent(false);\n\n    return true;\n}\n\nbool CHyprGroupBarDecoration::onInputOnDeco(const eInputType type, const Vector2D& mouseCoords, std::any data) {\n    switch (type) {\n        case INPUT_TYPE_AXIS: return onScrollOnDeco(mouseCoords, std::any_cast<const IPointer::SAxisEvent>(data));\n        case INPUT_TYPE_BUTTON: return onMouseButtonOnDeco(mouseCoords, std::any_cast<const IPointer::SButtonEvent&>(data));\n        case INPUT_TYPE_DRAG_START: return onBeginWindowDragOnDeco(mouseCoords);\n        case INPUT_TYPE_DRAG_END: return onEndWindowDragOnDeco(mouseCoords, std::any_cast<PHLWINDOW>(data));\n        default: return false;\n    }\n}\n\neDecorationLayer CHyprGroupBarDecoration::getDecorationLayer() {\n    return DECORATION_LAYER_OVER;\n}\n\nuint64_t CHyprGroupBarDecoration::getDecorationFlags() {\n    return DECORATION_ALLOWS_MOUSE_INPUT;\n}\n\nstd::string CHyprGroupBarDecoration::getDisplayName() {\n    return \"GroupBar\";\n}\n\nCBox CHyprGroupBarDecoration::assignedBoxGlobal() {\n    CBox box = m_assignedBox;\n    box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_TOP, m_window));\n\n    const auto PWORKSPACE = m_window->m_workspace;\n\n    if (PWORKSPACE && !m_window->m_pinned)\n        box.translate(PWORKSPACE->m_renderOffset->value());\n\n    return box.round();\n}\n\nbool CHyprGroupBarDecoration::visible() {\n    static auto PENABLED = CConfigValue<Hyprlang::INT>(\"group:groupbar:enabled\");\n    return *PENABLED && m_window->m_ruleApplicator->decorate().valueOrDefault();\n}\n"
  },
  {
    "path": "src/render/decorations/CHyprGroupBarDecoration.hpp",
    "content": "#pragma once\n\n#include \"IHyprWindowDecoration.hpp\"\n#include \"../../devices/IPointer.hpp\"\n#include <vector>\n#include \"../Texture.hpp\"\n#include <string>\n#include \"../../helpers/memory/Memory.hpp\"\n\nclass CTitleTex {\n  public:\n    CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float monitorScale);\n    ~CTitleTex() = default;\n\n    SP<ITexture> m_texActive;\n    SP<ITexture> m_texInactive;\n    SP<ITexture> m_texLockedActive;\n    SP<ITexture> m_texLockedInactive;\n    std::string  m_content;\n\n    PHLWINDOWREF m_windowOwner;\n};\n\nvoid refreshGroupBarGradients();\n\nclass CHyprGroupBarDecoration : public IHyprWindowDecoration {\n  public:\n    CHyprGroupBarDecoration(PHLWINDOW);\n    virtual ~CHyprGroupBarDecoration() = default;\n\n    virtual SDecorationPositioningInfo getPositioningInfo();\n\n    virtual void                       onPositioningReply(const SDecorationPositioningReply& reply);\n\n    virtual void                       draw(PHLMONITOR, float const& a);\n\n    virtual eDecorationType            getDecorationType();\n\n    virtual void                       updateWindow(PHLWINDOW);\n\n    virtual void                       damageEntire();\n\n    virtual bool                       onInputOnDeco(const eInputType, const Vector2D&, std::any = {});\n\n    virtual eDecorationLayer           getDecorationLayer();\n\n    virtual uint64_t                   getDecorationFlags();\n\n    virtual std::string                getDisplayName();\n\n  private:\n    CBox                      m_assignedBox = {0};\n\n    PHLWINDOWREF              m_window;\n\n    std::vector<PHLWINDOWREF> m_dwGroupMembers;\n\n    float                     m_barWidth;\n    float                     m_barHeight;\n\n    bool                      m_bLastVisibilityStatus = true;\n\n    CTitleTex*                textureFromTitle(const std::string&);\n    void                      invalidateTextures();\n\n    CBox                      assignedBoxGlobal();\n    bool                      visible();\n\n    bool                      onBeginWindowDragOnDeco(const Vector2D&);\n    bool                      onEndWindowDragOnDeco(const Vector2D&, PHLWINDOW);\n    bool                      onMouseButtonOnDeco(const Vector2D&, const IPointer::SButtonEvent&);\n    bool                      onScrollOnDeco(const Vector2D&, const IPointer::SAxisEvent);\n\n    struct STitleTexs {\n        // STitleTexs*                            overriden = nullptr; // TODO: make shit shared in-group to decrease VRAM usage.\n        std::vector<UP<CTitleTex>> titleTexs;\n    } m_titleTexs;\n};\n"
  },
  {
    "path": "src/render/decorations/DecorationPositioner.cpp",
    "content": "#include \"DecorationPositioner.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../layout/target/Target.hpp\"\n#include \"../../event/EventBus.hpp\"\n\nCDecorationPositioner::CDecorationPositioner() {\n    static auto P  = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { onWindowUnmap(window); });\n    static auto P2 = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { onWindowMap(window); });\n}\n\nVector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF pWindow) {\n    if (!pWindow) {\n        Log::logger->log(Log::ERR, \"getEdgeDefinedPoint: invalid pWindow\");\n        return {};\n    }\n\n    const bool TOP    = edges & DECORATION_EDGE_TOP;\n    const bool BOTTOM = edges & DECORATION_EDGE_BOTTOM;\n    const bool LEFT   = edges & DECORATION_EDGE_LEFT;\n    const bool RIGHT  = edges & DECORATION_EDGE_RIGHT;\n\n    const int  EDGESNO = TOP + BOTTOM + LEFT + RIGHT;\n\n    if (EDGESNO == 0 || EDGESNO == 3 || EDGESNO > 4) {\n        Log::logger->log(Log::ERR, \"getEdgeDefinedPoint: invalid number of edges\");\n        return {};\n    }\n\n    CBox wb = pWindow->getWindowMainSurfaceBox();\n\n    if (EDGESNO == 4)\n        return wb.pos();\n\n    if (EDGESNO == 1) {\n        if (TOP)\n            return wb.pos() + Vector2D{wb.size().x / 2.0, 0.0};\n        else if (BOTTOM)\n            return wb.pos() + Vector2D{wb.size().x / 2.0, wb.size().y};\n        else if (LEFT)\n            return wb.pos() + Vector2D{0.0, wb.size().y / 2.0};\n        else if (RIGHT)\n            return wb.pos() + Vector2D{wb.size().x, wb.size().y / 2.0};\n    } else {\n        if (TOP && LEFT)\n            return wb.pos();\n        if (TOP && RIGHT)\n            return wb.pos() + Vector2D{wb.size().x, 0.0};\n        if (BOTTOM && RIGHT)\n            return wb.pos() + wb.size();\n        if (BOTTOM && LEFT)\n            return wb.pos() + Vector2D{0.0, wb.size().y};\n    }\n    Log::logger->log(Log::ERR, \"getEdgeDefinedPoint: invalid configuration of edges\");\n    return {};\n}\n\nvoid CDecorationPositioner::uncacheDecoration(IHyprWindowDecoration* deco) {\n    std::erase_if(m_windowPositioningDatas, [&](const auto& data) { return !data->pWindow.lock() || data->pDecoration == deco; });\n\n    const auto WIT = std::ranges::find_if(m_windowDatas, [&](const auto& other) { return other.first.lock() == deco->m_window.lock(); });\n    if (WIT == m_windowDatas.end())\n        return;\n\n    WIT->second.needsRecalc = true;\n}\n\nvoid CDecorationPositioner::repositionDeco(IHyprWindowDecoration* deco) {\n    uncacheDecoration(deco);\n    onWindowUpdate(deco->m_window.lock());\n}\n\nCDecorationPositioner::SWindowPositioningData* CDecorationPositioner::getDataFor(IHyprWindowDecoration* pDecoration, PHLWINDOW pWindow) {\n    auto it = std::ranges::find_if(m_windowPositioningDatas, [&](const auto& el) { return el->pDecoration == pDecoration; });\n\n    if (it != m_windowPositioningDatas.end())\n        return it->get();\n\n    const auto DATA = m_windowPositioningDatas.emplace_back(makeUnique<CDecorationPositioner::SWindowPositioningData>(pWindow, pDecoration)).get();\n\n    DATA->positioningInfo = pDecoration->getPositioningInfo();\n\n    return DATA;\n}\n\nvoid CDecorationPositioner::sanitizeDatas() {\n    std::erase_if(m_windowDatas, [](const auto& other) { return !valid(other.first); });\n    std::erase_if(m_windowPositioningDatas, [](const auto& other) {\n        if (!validMapped(other->pWindow))\n            return true;\n        if (std::ranges::find_if(other->pWindow->m_windowDecorations, [&](const auto& el) { return el.get() == other->pDecoration; }) == other->pWindow->m_windowDecorations.end())\n            return true;\n        return false;\n    });\n}\n\nvoid CDecorationPositioner::forceRecalcFor(PHLWINDOW pWindow) {\n    const auto WIT = std::ranges::find_if(m_windowDatas, [&](const auto& other) { return other.first.lock() == pWindow; });\n    if (WIT == m_windowDatas.end())\n        return;\n\n    const auto WINDOWDATA = &WIT->second;\n\n    WINDOWDATA->needsRecalc = true;\n}\n\nvoid CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) {\n    if (!validMapped(pWindow))\n        return;\n\n    const auto WIT = std::ranges::find_if(m_windowDatas, [&](const auto& other) { return other.first.lock() == pWindow; });\n    if (WIT == m_windowDatas.end())\n        return;\n\n    const auto WINDOWDATA = &WIT->second;\n\n    sanitizeDatas();\n\n    //\n    std::vector<CDecorationPositioner::SWindowPositioningData*> datas;\n    // reserve to avoid reallocations\n    datas.reserve(pWindow->m_windowDecorations.size());\n\n    for (auto const& wd : pWindow->m_windowDecorations) {\n        datas.push_back(getDataFor(wd.get(), pWindow));\n    }\n\n    if (WINDOWDATA->lastWindowSize == pWindow->m_realSize->value() /* position not changed */\n        && std::ranges::all_of(m_windowPositioningDatas, [pWindow](const auto& data) { return pWindow != data->pWindow.lock() || !data->needsReposition; })\n        /* all window datas are either not for this window or don't need a reposition */\n        && !WINDOWDATA->needsRecalc /* window doesn't need recalc */\n    )\n        return;\n\n    for (auto const& wd : datas) {\n        wd->positioningInfo = wd->pDecoration->getPositioningInfo();\n    }\n\n    WINDOWDATA->lastWindowSize = pWindow->m_realSize->value();\n    WINDOWDATA->needsRecalc    = false;\n    const bool EPHEMERAL       = pWindow->m_realSize->isBeingAnimated();\n\n    std::ranges::sort(datas, [](const auto& a, const auto& b) { return a->positioningInfo.priority > b->positioningInfo.priority; });\n\n    CBox wb = pWindow->getWindowMainSurfaceBox();\n\n    // calc reserved\n    float reservedXL = 0, reservedYT = 0, reservedXR = 0, reservedYB = 0;\n    for (size_t i = 0; i < datas.size(); ++i) {\n        auto* const wd = datas[i];\n\n        if (!wd->positioningInfo.reserved)\n            continue;\n\n        const bool TOP    = wd->positioningInfo.edges & DECORATION_EDGE_TOP;\n        const bool BOTTOM = wd->positioningInfo.edges & DECORATION_EDGE_BOTTOM;\n        const bool LEFT   = wd->positioningInfo.edges & DECORATION_EDGE_LEFT;\n        const bool RIGHT  = wd->positioningInfo.edges & DECORATION_EDGE_RIGHT;\n\n        if (LEFT)\n            reservedXL += wd->positioningInfo.desiredExtents.topLeft.x;\n        if (RIGHT)\n            reservedXR += wd->positioningInfo.desiredExtents.bottomRight.x;\n        if (TOP)\n            reservedYT += wd->positioningInfo.desiredExtents.topLeft.y;\n        if (BOTTOM)\n            reservedYB += wd->positioningInfo.desiredExtents.bottomRight.y;\n    }\n\n    WINDOWDATA->reserved = {{reservedXL, reservedYT}, {reservedXR, reservedYB}};\n\n    float stickyOffsetXL = 0, stickyOffsetYT = 0, stickyOffsetXR = 0, stickyOffsetYB = 0;\n\n    for (size_t i = 0; i < datas.size(); ++i) {\n        auto* const wd = datas[i];\n\n        wd->needsReposition = false;\n\n        const bool TOP     = wd->positioningInfo.edges & DECORATION_EDGE_TOP;\n        const bool BOTTOM  = wd->positioningInfo.edges & DECORATION_EDGE_BOTTOM;\n        const bool LEFT    = wd->positioningInfo.edges & DECORATION_EDGE_LEFT;\n        const bool RIGHT   = wd->positioningInfo.edges & DECORATION_EDGE_RIGHT;\n        const int  EDGESNO = TOP + BOTTOM + LEFT + RIGHT;\n        const bool SOLID   = !(wd->pDecoration->getDecorationFlags() & DECORATION_NON_SOLID);\n\n        if (wd->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) {\n\n            if (SOLID) {\n                if (LEFT)\n                    stickyOffsetXL += wd->positioningInfo.desiredExtents.topLeft.x;\n                if (RIGHT)\n                    stickyOffsetXR += wd->positioningInfo.desiredExtents.bottomRight.x;\n                if (TOP)\n                    stickyOffsetYT += wd->positioningInfo.desiredExtents.topLeft.y;\n                if (BOTTOM)\n                    stickyOffsetYB += wd->positioningInfo.desiredExtents.bottomRight.y;\n            }\n\n            wd->lastReply = {};\n            wd->pDecoration->onPositioningReply({});\n            continue;\n        }\n\n        if (wd->positioningInfo.policy == DECORATION_POSITION_STICKY) {\n            if (EDGESNO != 1 && EDGESNO != 4) {\n                wd->lastReply = {};\n                wd->pDecoration->onPositioningReply({});\n                continue;\n            }\n\n            const auto desiredExtents = wd->positioningInfo.desiredExtents;\n\n            const auto EDGEPOINT = getEdgeDefinedPoint(wd->positioningInfo.edges, pWindow);\n\n            Vector2D   pos, size;\n\n            if (EDGESNO == 4) {\n                stickyOffsetXL += desiredExtents.topLeft.x;\n                stickyOffsetXR += desiredExtents.bottomRight.x;\n                stickyOffsetYT += desiredExtents.topLeft.y;\n                stickyOffsetYB += desiredExtents.bottomRight.y;\n\n                pos  = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT};\n                size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR, stickyOffsetYB + stickyOffsetYT};\n            } else if (LEFT) {\n                const auto desiredSize = desiredExtents.topLeft.x;\n\n                pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, -stickyOffsetYT};\n                pos.x -= desiredSize;\n                size = {sc<double>(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT};\n\n                if (SOLID)\n                    stickyOffsetXL += desiredSize;\n            } else if (RIGHT) {\n                const auto desiredSize = desiredExtents.bottomRight.x;\n\n                pos  = wb.pos() + Vector2D{wb.size().x, 0.0} - EDGEPOINT + Vector2D{stickyOffsetXR, -stickyOffsetYT};\n                size = {sc<double>(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT};\n\n                if (SOLID)\n                    stickyOffsetXR += desiredSize;\n            } else if (TOP) {\n                const auto desiredSize = desiredExtents.topLeft.y;\n\n                pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT};\n                pos.y -= desiredSize;\n                size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc<double>(desiredSize)};\n\n                if (SOLID)\n                    stickyOffsetYT += desiredSize;\n            } else {\n                const auto desiredSize = desiredExtents.bottomRight.y;\n\n                pos  = wb.pos() + Vector2D{0.0, wb.size().y} - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYB};\n                size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc<double>(desiredSize)};\n\n                if (SOLID)\n                    stickyOffsetYB += desiredSize;\n            }\n\n            wd->lastReply = {{pos, size}, EPHEMERAL};\n            wd->pDecoration->onPositioningReply(wd->lastReply);\n\n            continue;\n        } else {\n            // invalid\n            wd->lastReply = {};\n            wd->pDecoration->onPositioningReply({});\n            continue;\n        }\n    }\n\n    if (WINDOWDATA->extents != SBoxExtents{{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}) {\n        WINDOWDATA->extents = {{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}};\n        pWindow->layoutTarget()->recalc();\n    }\n}\n\nvoid CDecorationPositioner::onWindowUnmap(PHLWINDOW pWindow) {\n    std::erase_if(m_windowPositioningDatas, [&](const auto& data) { return data->pWindow.lock() == pWindow; });\n    m_windowDatas.erase(pWindow);\n}\n\nvoid CDecorationPositioner::onWindowMap(PHLWINDOW pWindow) {\n    m_windowDatas[pWindow] = {};\n}\n\nSBoxExtents CDecorationPositioner::getWindowDecorationReserved(PHLWINDOWREF pWindow) {\n    try {\n        const auto E = m_windowDatas.at(pWindow);\n        return E.reserved;\n    } catch (std::out_of_range& e) { return {}; }\n}\n\nSBoxExtents CDecorationPositioner::getWindowDecorationExtents(PHLWINDOWREF pWindow, bool inputOnly) {\n    CBox const mainSurfaceBox = pWindow->getWindowMainSurfaceBox();\n    CBox       accum          = mainSurfaceBox;\n\n    for (auto const& data : m_windowPositioningDatas) {\n        if (!data->pDecoration || (inputOnly && !(data->pDecoration->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT)))\n            continue;\n\n        auto const window = data->pWindow;\n        if (!window || window != pWindow)\n            continue;\n\n        CBox decoBox;\n        if (data->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) {\n            decoBox = mainSurfaceBox;\n            decoBox.addExtents(data->positioningInfo.desiredExtents);\n        } else {\n            decoBox = data->lastReply.assignedGeometry;\n            decoBox.translate(getEdgeDefinedPoint(data->positioningInfo.edges, pWindow));\n        }\n\n        // Check bounds only if decoBox extends beyond accum\n        SBoxExtents extentsToAdd;\n        bool        needUpdate = false;\n\n        if (decoBox.x < accum.x) {\n            extentsToAdd.topLeft.x = accum.x - decoBox.x;\n            needUpdate             = true;\n        }\n        if (decoBox.y < accum.y) {\n            extentsToAdd.topLeft.y = accum.y - decoBox.y;\n            needUpdate             = true;\n        }\n        if (decoBox.x + decoBox.w > accum.x + accum.w) {\n            extentsToAdd.bottomRight.x = (decoBox.x + decoBox.w) - (accum.x + accum.w);\n            needUpdate                 = true;\n        }\n        if (decoBox.y + decoBox.h > accum.y + accum.h) {\n            extentsToAdd.bottomRight.y = (decoBox.y + decoBox.h) - (accum.y + accum.h);\n            needUpdate                 = true;\n        }\n\n        if (needUpdate)\n            accum.addExtents(extentsToAdd);\n    }\n\n    return accum.extentsFrom(mainSurfaceBox);\n}\n\nCBox CDecorationPositioner::getBoxWithIncludedDecos(PHLWINDOW pWindow) {\n    CBox accum = pWindow->getWindowMainSurfaceBox();\n\n    for (auto const& data : m_windowPositioningDatas) {\n        if (data->pWindow.lock() != pWindow)\n            continue;\n\n        if (!(data->pDecoration->getDecorationFlags() & DECORATION_PART_OF_MAIN_WINDOW))\n            continue;\n\n        CBox decoBox;\n\n        if (data->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) {\n            decoBox = data->pWindow->getWindowMainSurfaceBox();\n            decoBox.addExtents(data->positioningInfo.desiredExtents);\n        } else {\n            decoBox              = data->lastReply.assignedGeometry;\n            const auto EDGEPOINT = getEdgeDefinedPoint(data->positioningInfo.edges, pWindow);\n            decoBox.translate(EDGEPOINT);\n        }\n\n        SBoxExtents extentsToAdd;\n\n        if (decoBox.x < accum.x)\n            extentsToAdd.topLeft.x = accum.x - decoBox.x;\n        if (decoBox.y < accum.y)\n            extentsToAdd.topLeft.y = accum.y - decoBox.y;\n        if (decoBox.x + decoBox.w > accum.x + accum.w)\n            extentsToAdd.bottomRight.x = (decoBox.x + decoBox.w) - (accum.x + accum.w);\n        if (decoBox.y + decoBox.h > accum.y + accum.h)\n            extentsToAdd.bottomRight.y = (decoBox.y + decoBox.h) - (accum.y + accum.h);\n\n        accum.addExtents(extentsToAdd);\n    }\n\n    return accum;\n}\n\nCBox CDecorationPositioner::getWindowDecorationBox(IHyprWindowDecoration* deco) {\n    auto const window = deco->m_window.lock();\n    const auto DATA   = getDataFor(deco, window);\n\n    CBox       box = DATA->lastReply.assignedGeometry;\n    box.translate(getEdgeDefinedPoint(DATA->positioningInfo.edges, window));\n    return box;\n}\n"
  },
  {
    "path": "src/render/decorations/DecorationPositioner.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <vector>\n#include <map>\n#include \"../../helpers/math/Math.hpp\"\n#include \"../../desktop/DesktopTypes.hpp\"\n\nclass IHyprWindowDecoration;\n\nenum eDecorationPositioningPolicy : uint8_t {\n    DECORATION_POSITION_ABSOLUTE = 0, /* Decoration wants absolute positioning */\n    DECORATION_POSITION_STICKY,       /* Decoration is stuck to some edge of a window */\n};\n\nenum eDecorationEdges : uint8_t {\n    DECORATION_EDGE_TOP    = 1 << 0,\n    DECORATION_EDGE_BOTTOM = 1 << 1,\n    DECORATION_EDGE_LEFT   = 1 << 2,\n    DECORATION_EDGE_RIGHT  = 1 << 3\n};\n\n/*\nRequest the positioner to position a decoration\n\nDECORATION_POSITION_ABSOLUTE:\n    - desiredExtents has to contain the extents. Edges has to have the edges used.\n    - reserved allowed\nDECORATION_POSITION_STICKY:\n    - one edge allowed\n    - priority allowed\n    - desiredExtents contains the desired extents. Any other edge than the one selected is ignored.\n    - reserved is allowed\n*/\nstruct SDecorationPositioningInfo {\n    eDecorationPositioningPolicy policy   = DECORATION_POSITION_ABSOLUTE;\n    uint32_t                     edges    = 0;  // enum eDecorationEdges\n    uint32_t                     priority = 10; // priority, decos will be evaluated high -> low\n    SBoxExtents                  desiredExtents;\n    bool                         reserved = false; // if true, geometry will use reserved area\n};\n\n/*\nA reply from the positioner. This may be sent multiple times, if anything changes.\n\nDECORATION_POSITION_ABSOLUTE:\n    - assignedGeometry is empty\nDECORATION_POSITION_STICKY:\n    - assignedGeometry is relative to the edge's center point\n    - ephemeral is sent\n*/\nstruct SDecorationPositioningReply {\n    CBox assignedGeometry;\n    bool ephemeral = false; // if true, means it's a result of an animation and will change soon.\n};\n\nclass CDecorationPositioner {\n  public:\n    CDecorationPositioner();\n\n    Vector2D getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF pWindow);\n\n    // called on resize, or insert/removal of a new deco\n    void        onWindowUpdate(PHLWINDOW pWindow);\n    void        uncacheDecoration(IHyprWindowDecoration* deco);\n    SBoxExtents getWindowDecorationReserved(PHLWINDOWREF pWindow);\n    SBoxExtents getWindowDecorationExtents(PHLWINDOWREF pWindow, bool inputOnly = false);\n    CBox        getBoxWithIncludedDecos(PHLWINDOW pWindow);\n    void        repositionDeco(IHyprWindowDecoration* deco);\n    CBox        getWindowDecorationBox(IHyprWindowDecoration* deco);\n    void        forceRecalcFor(PHLWINDOW pWindow);\n\n  private:\n    struct SWindowPositioningData {\n        PHLWINDOWREF                pWindow;\n        IHyprWindowDecoration*      pDecoration = nullptr;\n        SDecorationPositioningInfo  positioningInfo;\n        SDecorationPositioningReply lastReply;\n        bool                        needsReposition = true;\n    };\n\n    struct SWindowData {\n        Vector2D    lastWindowSize = {};\n        SBoxExtents reserved       = {};\n        SBoxExtents extents        = {};\n        bool        needsRecalc    = false;\n    };\n\n    std::map<PHLWINDOWREF, SWindowData>     m_windowDatas;\n    std::vector<UP<SWindowPositioningData>> m_windowPositioningDatas;\n\n    SWindowPositioningData*                 getDataFor(IHyprWindowDecoration* pDecoration, PHLWINDOW pWindow);\n    void                                    onWindowUnmap(PHLWINDOW pWindow);\n    void                                    onWindowMap(PHLWINDOW pWindow);\n    void                                    sanitizeDatas();\n};\n\ninline UP<CDecorationPositioner> g_pDecorationPositioner;\n"
  },
  {
    "path": "src/render/decorations/IHyprWindowDecoration.cpp",
    "content": "#include \"IHyprWindowDecoration.hpp\"\n\nIHyprWindowDecoration::IHyprWindowDecoration(PHLWINDOW pWindow) : m_window(pWindow) {\n    ;\n}\n\nbool IHyprWindowDecoration::onInputOnDeco(const eInputType, const Vector2D&, std::any) {\n    return false;\n}\n\neDecorationLayer IHyprWindowDecoration::getDecorationLayer() {\n    return DECORATION_LAYER_UNDER;\n}\n\nuint64_t IHyprWindowDecoration::getDecorationFlags() {\n    return 0;\n}\n\nstd::string IHyprWindowDecoration::getDisplayName() {\n    return \"Unknown Decoration\";\n}\n"
  },
  {
    "path": "src/render/decorations/IHyprWindowDecoration.hpp",
    "content": "#pragma once\n\n#include <any>\n#include \"../../defines.hpp\"\n#include \"../../helpers/math/Math.hpp\"\n#include \"DecorationPositioner.hpp\"\n\nenum eDecorationType : int8_t {\n    DECORATION_NONE = -1,\n    DECORATION_GROUPBAR,\n    DECORATION_SHADOW,\n    DECORATION_BORDER,\n    DECORATION_CUSTOM\n};\n\nenum eDecorationLayer : uint8_t {\n    DECORATION_LAYER_BOTTOM = 0, /* lowest. */\n    DECORATION_LAYER_UNDER,      /* under the window, but above BOTTOM */\n    DECORATION_LAYER_OVER,       /* above the window, but below its popups */\n    DECORATION_LAYER_OVERLAY     /* above everything of the window, including popups */\n};\n\nenum eDecorationFlags : uint8_t {\n    DECORATION_ALLOWS_MOUSE_INPUT  = 1 << 0, /* this decoration accepts mouse input */\n    DECORATION_PART_OF_MAIN_WINDOW = 1 << 1, /* this decoration is a *seamless* part of the main window, so stuff like shadows will include it */\n    DECORATION_NON_SOLID           = 1 << 2, /* this decoration is not solid. Other decorations should draw on top of it. Example: shadow */\n};\n\nclass CMonitor;\nclass CDecorationPositioner;\n\nclass IHyprWindowDecoration {\n  public:\n    IHyprWindowDecoration(PHLWINDOW);\n    virtual ~IHyprWindowDecoration() = default;\n\n    virtual SDecorationPositioningInfo getPositioningInfo() = 0;\n\n    virtual void                       onPositioningReply(const SDecorationPositioningReply& reply) = 0;\n\n    virtual void                       draw(PHLMONITOR, float const& a) = 0;\n\n    virtual eDecorationType            getDecorationType() = 0;\n\n    virtual void                       updateWindow(PHLWINDOW) = 0;\n\n    virtual void                       damageEntire() = 0; // should be ignored by non-absolute decos\n\n    virtual bool                       onInputOnDeco(const eInputType, const Vector2D&, std::any = {});\n\n    virtual eDecorationLayer           getDecorationLayer();\n\n    virtual uint64_t                   getDecorationFlags();\n\n    virtual std::string                getDisplayName();\n\n  private:\n    PHLWINDOWREF m_window;\n\n    friend class CDecorationPositioner;\n};\n"
  },
  {
    "path": "src/render/gl/GLFramebuffer.cpp",
    "content": "#include \"GLFramebuffer.hpp\"\n#include \"../OpenGL.hpp\"\n#include \"../Renderer.hpp\"\n#include \"macros.hpp\"\n#include \"../Framebuffer.hpp\"\n\nCGLFramebuffer::CGLFramebuffer() : IFramebuffer() {}\nCGLFramebuffer::CGLFramebuffer(const std::string& name) : IFramebuffer(name) {}\n\nbool CGLFramebuffer::internalAlloc(int w, int h, uint32_t drmFormat) {\n    g_pHyprOpenGL->makeEGLCurrent();\n\n    bool firstAlloc = false;\n\n    if (!m_tex) {\n        m_tex = g_pHyprRenderer->createTexture();\n        m_tex->allocate({w, h});\n        m_tex->bind();\n        m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n        m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n        m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n        m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n        firstAlloc = true;\n    }\n\n    if (!m_fbAllocated) {\n        glGenFramebuffers(1, &m_fb);\n        m_fbAllocated = true;\n        firstAlloc    = true;\n    }\n\n    if (firstAlloc) {\n        const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat);\n        m_tex->bind();\n        glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, w, h, 0, format->glFormat, format->glType, nullptr);\n        glBindFramebuffer(GL_FRAMEBUFFER, m_fb);\n        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0);\n\n        if (m_stencilTex && m_stencilTex->ok()) {\n            m_stencilTex->bind();\n            glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);\n            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0);\n\n            glDisable(GL_DEPTH_TEST);\n            glDepthMask(GL_FALSE);\n        }\n\n        auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n        RASSERT((status == GL_FRAMEBUFFER_COMPLETE), \"Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})\", status, sc<int>(glGetError()));\n\n        if (m_stencilTex && m_stencilTex->ok())\n            m_stencilTex->unbind();\n\n        Log::logger->log(Log::DEBUG, \"Framebuffer created, status {}\", status);\n    }\n\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glBindFramebuffer(GL_FRAMEBUFFER, 0);\n\n    return true;\n}\n\nvoid CGLFramebuffer::addStencil(SP<ITexture> tex) {\n    if (m_stencilTex == tex)\n        return;\n\n    RASSERT(!m_fbAllocated, \"Should add stencil tex prior to FB allocation\")\n    m_stencilTex = tex;\n}\n\nvoid CGLFramebuffer::bind() {\n    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb);\n\n    if (g_pHyprOpenGL) {\n        const auto& size = g_pHyprRenderer->m_renderData.pMonitor ? g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize : m_size;\n        g_pHyprOpenGL->setViewport(0, 0, size.x, size.y);\n    } else\n        glViewport(0, 0, m_size.x, m_size.y);\n}\n\nvoid CGLFramebuffer::unbind() {\n    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);\n}\n\nvoid CGLFramebuffer::release() {\n    if (m_fbAllocated) {\n        glBindFramebuffer(GL_FRAMEBUFFER, m_fb);\n        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);\n        glBindFramebuffer(GL_FRAMEBUFFER, 0);\n\n        glDeleteFramebuffers(1, &m_fb);\n        m_fbAllocated = false;\n        m_fb          = 0;\n    }\n\n    if (m_tex)\n        m_tex.reset();\n\n    m_size = Vector2D();\n}\n\nbool CGLFramebuffer::readPixels(CHLBufferReference buffer, uint32_t offsetX, uint32_t offsetY, uint32_t width, uint32_t height) {\n    auto shm                      = buffer->shm();\n    auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); // no need for end, cuz it's shm\n\n    const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format);\n    if (!PFORMAT) {\n        LOGM(Log::ERR, \"Can't copy: failed to find a pixel format\");\n        return false;\n    }\n\n    g_pHyprOpenGL->makeEGLCurrent();\n    glBindFramebuffer(GL_READ_FRAMEBUFFER, getFBID());\n    bind();\n\n    glPixelStorei(GL_PACK_ALIGNMENT, 1);\n\n    uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_size.x);\n    int      glFormat   = PFORMAT->glFormat;\n\n    if (glFormat == GL_RGBA)\n        glFormat = GL_BGRA_EXT;\n\n    if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) {\n        if (PFORMAT->swizzle.has_value()) {\n            std::array<GLint, 4> RGBA = SWIZZLE_RGBA;\n            std::array<GLint, 4> BGRA = SWIZZLE_BGRA;\n            if (PFORMAT->swizzle == RGBA)\n                glFormat = GL_RGBA;\n            else if (PFORMAT->swizzle == BGRA)\n                glFormat = GL_BGRA_EXT;\n            else {\n                LOGM(Log::ERR, \"Copied frame via shm might be broken or color flipped\");\n                glFormat = GL_RGBA;\n            }\n        }\n    }\n\n    // This could be optimized by using a pixel buffer object to make this async,\n    // but really clients should just use a dma buffer anyways.\n    if (packStride == sc<uint32_t>(shm.stride)) {\n        glReadPixels(offsetX, offsetY, width > 0 ? width : m_size.x, height > 0 ? height : m_size.y, glFormat, PFORMAT->glType, pixelData);\n    } else {\n        const auto h = height > 0 ? height : m_size.y;\n        for (size_t i = 0; i < h; ++i) {\n            uint32_t y = i;\n            glReadPixels(offsetX, offsetY + y, width > 0 ? width : m_size.x, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride);\n        }\n    }\n\n    unbind();\n    glPixelStorei(GL_PACK_ALIGNMENT, 4);\n\n    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);\n    return true;\n}\n\nCGLFramebuffer::~CGLFramebuffer() {\n    release();\n}\n\nGLuint CGLFramebuffer::getFBID() {\n    return m_fbAllocated ? m_fb : 0;\n}\n\nvoid CGLFramebuffer::invalidate(const std::vector<GLenum>& attachments) {\n    if (!isAllocated())\n        return;\n\n    glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data());\n}\n"
  },
  {
    "path": "src/render/gl/GLFramebuffer.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include \"../Texture.hpp\"\n#include \"../Framebuffer.hpp\"\n#include <drm_fourcc.h>\n\nclass CGLFramebuffer : public IFramebuffer {\n  public:\n    CGLFramebuffer();\n    CGLFramebuffer(const std::string& name);\n    ~CGLFramebuffer();\n\n    void   addStencil(SP<ITexture> tex) override;\n    void   release() override;\n    bool   readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) override;\n\n    void   bind() override;\n    void   unbind();\n    GLuint getFBID();\n    void   invalidate(const std::vector<GLenum>& attachments);\n\n  protected:\n    bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) override;\n\n  private:\n    GLuint m_fb = -1;\n\n    friend class CGLRenderbuffer;\n};\n"
  },
  {
    "path": "src/render/gl/GLRenderbuffer.cpp",
    "content": "#include \"GLRenderbuffer.hpp\"\n#include \"../Renderer.hpp\"\n#include \"../OpenGL.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../Framebuffer.hpp\"\n#include \"GLFramebuffer.hpp\"\n#include \"../Renderbuffer.hpp\"\n#include <hyprutils/memory/SharedPtr.hpp>\n#include <hyprutils/signal/Listener.hpp>\n#include <hyprutils/signal/Signal.hpp>\n\n#include <dlfcn.h>\n\nCGLRenderbuffer::~CGLRenderbuffer() {\n    if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer)\n        return;\n\n    g_pHyprOpenGL->makeEGLCurrent();\n\n    unbind();\n    m_framebuffer->release();\n\n    if (m_rbo)\n        glDeleteRenderbuffers(1, &m_rbo);\n\n    if (m_image != EGL_NO_IMAGE_KHR)\n        g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image);\n}\n\nCGLRenderbuffer::CGLRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t format) : IRenderbuffer(buffer, format) {\n    auto dma = buffer->dmabuf();\n\n    m_image = g_pHyprOpenGL->createEGLImage(dma);\n    if (m_image == EGL_NO_IMAGE_KHR) {\n        Log::logger->log(Log::ERR, \"rb: createEGLImage failed\");\n        return;\n    }\n\n    glGenRenderbuffers(1, &m_rbo);\n    glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);\n    g_pHyprOpenGL->m_proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, m_image);\n    glBindRenderbuffer(GL_RENDERBUFFER, 0);\n\n    m_framebuffer = makeShared<CGLFramebuffer>();\n    glGenFramebuffers(1, &GLFB(m_framebuffer)->m_fb);\n    GLFB(m_framebuffer)->m_fbAllocated = true;\n    m_framebuffer->m_size              = buffer->size;\n    m_framebuffer->m_drmFormat         = dma.format;\n    m_framebuffer->bind();\n    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);\n\n    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {\n        Log::logger->log(Log::ERR, \"rbo: glCheckFramebufferStatus failed\");\n        return;\n    }\n\n    GLFB(m_framebuffer)->unbind();\n\n    m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); });\n\n    m_good = true;\n}\n\nvoid CGLRenderbuffer::bind() {\n    g_pHyprOpenGL->makeEGLCurrent();\n    m_framebuffer->bind();\n}\n\nvoid CGLRenderbuffer::unbind() {\n    GLFB(m_framebuffer)->unbind();\n}\n"
  },
  {
    "path": "src/render/gl/GLRenderbuffer.hpp",
    "content": "#pragma once\n\n#include \"../../helpers/memory/Memory.hpp\"\n#include \"../Renderbuffer.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n\nclass CMonitor;\n\nclass CGLRenderbuffer : public IRenderbuffer {\n  public:\n    CGLRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t format);\n    ~CGLRenderbuffer();\n\n    void bind() override;\n    void unbind() override;\n\n  private:\n    void*  m_image = nullptr;\n    GLuint m_rbo   = 0;\n};\n"
  },
  {
    "path": "src/render/gl/GLTexture.cpp",
    "content": "#include \"GLTexture.hpp\"\n#include \"../Renderer.hpp\"\n#include \"../../Compositor.hpp\"\n#include \"../../helpers/Format.hpp\"\n#include \"../Texture.hpp\"\n#include <cstring>\n\nCGLTexture::CGLTexture(bool opaque) {\n    m_opaque = opaque;\n}\n\nCGLTexture::~CGLTexture() {\n    if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer)\n        return;\n\n    g_pHyprOpenGL->makeEGLCurrent();\n    if (m_texID) {\n        GLCALL(glDeleteTextures(1, &m_texID));\n        m_texID = 0;\n    }\n\n    if (m_eglImage)\n        g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_eglImage);\n    m_eglImage = nullptr;\n    m_cachedStates.fill(std::nullopt);\n}\n\nCGLTexture::CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_, bool keepDataCopy, bool opaque) :\n    ITexture(drmFormat, pixels, stride, size_, keepDataCopy, opaque) {\n\n    g_pHyprOpenGL->makeEGLCurrent();\n\n    const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat);\n    ASSERT(format);\n\n    m_type          = format->withAlpha ? TEXTURE_RGBA : TEXTURE_RGBX;\n    m_size          = size_;\n    m_isSynchronous = true;\n    m_target        = GL_TEXTURE_2D;\n    allocate(size_);\n    bind();\n    setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n    if (format->swizzle.has_value())\n        swizzle(format->swizzle.value());\n\n    bool alignmentChanged = false;\n    if (format->bytesPerBlock != 4) {\n        const GLint alignment = (stride % 4 == 0) ? 4 : 1;\n        GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment));\n        alignmentChanged = true;\n    }\n\n    GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock));\n    GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, size_.x, size_.y, 0, format->glFormat, format->glType, pixels));\n    GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0));\n    if (alignmentChanged)\n        GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4));\n\n    unbind();\n}\n\nCGLTexture::CGLTexture(const Aquamarine::SDMABUFAttrs& attrs, void* image, bool opaque) {\n    m_opaque = opaque;\n    if (!g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES) {\n        Log::logger->log(Log::ERR, \"Cannot create a dmabuf texture: no glEGLImageTargetTexture2DOES\");\n        return;\n    }\n\n    m_opaque = NFormatUtils::isFormatOpaque(attrs.format);\n\n    // #TODO external only formats should be external aswell.\n    // also needs a seperate color shader.\n    /*if (NFormatUtils::isFormatYUV(attrs.format)) {\n        m_target = GL_TEXTURE_EXTERNAL_OES;\n        m_type   = TEXTURE_EXTERNAL;\n    } else {*/\n    m_target = GL_TEXTURE_2D;\n    m_type   = NFormatUtils::isFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA;\n    //}\n\n    allocate(attrs.size);\n    m_eglImage = image;\n\n    bind();\n    setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    GLCALL(g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES(m_target, image));\n    unbind();\n}\n\nCGLTexture::CGLTexture(std::span<const float> lut3D, size_t N) : ITexture(lut3D, N), m_target(GL_TEXTURE_3D) {\n    allocate({});\n    bind();\n\n    GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));\n    setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    setTexParameter(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);\n\n    // Expand RGB->RGBA on upload (alpha=1)\n    std::vector<float> rgba;\n    rgba.resize(N * N * N * 4);\n    for (size_t i = 0, j = 0; i < N * N * N; ++i, j += 3) {\n        rgba[i * 4 + 0] = lut3D[j + 0];\n        rgba[i * 4 + 1] = lut3D[j + 1];\n        rgba[i * 4 + 2] = lut3D[j + 2];\n        rgba[i * 4 + 3] = 1.F;\n    }\n\n    GLCALL(glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, N, N, N, 0, GL_RGBA, GL_FLOAT, rgba.data()));\n\n    unbind();\n}\n\nvoid CGLTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) {\n    if (damage.empty())\n        return;\n\n    g_pHyprOpenGL->makeEGLCurrent();\n\n    const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat);\n    ASSERT(format);\n\n    bind();\n\n    if (format->swizzle.has_value())\n        swizzle(format->swizzle.value());\n\n    bool alignmentChanged = false;\n    if (format->bytesPerBlock != 4) {\n        const GLint alignment = (stride % 4 == 0) ? 4 : 1;\n        GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment));\n        alignmentChanged = true;\n    }\n\n    GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock));\n\n    damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &pixels](const auto& rect) {\n        GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1));\n        GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1));\n\n        int width  = rect.x2 - rect.x1;\n        int height = rect.y2 - rect.y1;\n        GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels));\n    });\n\n    if (alignmentChanged)\n        GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4));\n\n    GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0));\n    GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0));\n    GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0));\n\n    unbind();\n\n    if (m_keepDataCopy) {\n        m_dataCopy.resize(stride * m_size.y);\n        memcpy(m_dataCopy.data(), pixels, stride * m_size.y);\n    }\n}\n\nvoid CGLTexture::allocate(const Vector2D& size, uint32_t drmFormat) {\n    if (!m_texID)\n        GLCALL(glGenTextures(1, &m_texID));\n    m_size      = size;\n    m_drmFormat = drmFormat;\n}\n\nvoid CGLTexture::bind() {\n    GLCALL(glBindTexture(m_target, m_texID));\n}\n\nvoid CGLTexture::unbind() {\n    GLCALL(glBindTexture(m_target, 0));\n}\n\nbool CGLTexture::ok() {\n    return m_texID > 0;\n}\n\nbool CGLTexture::isDMA() {\n    return m_eglImage;\n}\n\nconstexpr std::optional<size_t> CGLTexture::getCacheStateIndex(GLenum pname) {\n    switch (pname) {\n        case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S;\n        case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T;\n        case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER;\n        case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER;\n        case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R;\n        case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B;\n        default: return std::nullopt;\n    }\n}\n\nvoid CGLTexture::setTexParameter(GLenum pname, GLint param) {\n    const auto cacheIndex = getCacheStateIndex(pname);\n\n    if (!cacheIndex) {\n        GLCALL(glTexParameteri(m_target, pname, param));\n        return;\n    }\n\n    const auto idx = cacheIndex.value();\n\n    if (m_cachedStates[idx] == param)\n        return;\n\n    m_cachedStates[idx] = param;\n    GLCALL(glTexParameteri(m_target, pname, param));\n}\n\nvoid CGLTexture::swizzle(const std::array<GLint, 4>& colors) {\n    setTexParameter(GL_TEXTURE_SWIZZLE_R, colors.at(0));\n    setTexParameter(GL_TEXTURE_SWIZZLE_G, colors.at(1));\n    setTexParameter(GL_TEXTURE_SWIZZLE_B, colors.at(2));\n    setTexParameter(GL_TEXTURE_SWIZZLE_A, colors.at(3));\n}\n"
  },
  {
    "path": "src/render/gl/GLTexture.hpp",
    "content": "#pragma once\n\n#include \"../Texture.hpp\"\n#include <aquamarine/buffer/Buffer.hpp>\n#include <hyprutils/math/Misc.hpp>\n\nclass CGLTexture : public ITexture {\n  public:\n    using ITexture::ITexture;\n\n    CGLTexture(CGLTexture&)        = delete;\n    CGLTexture(CGLTexture&&)       = delete;\n    CGLTexture(const CGLTexture&&) = delete;\n    CGLTexture(const CGLTexture&)  = delete;\n\n    CGLTexture(bool opaque = false);\n    CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false);\n    CGLTexture(const Aquamarine::SDMABUFAttrs&, void* image, bool opaque = false);\n    CGLTexture(std::span<const float> lut3D, size_t N);\n    ~CGLTexture();\n\n    void allocate(const Vector2D& size, uint32_t drmFormat = 0) override;\n    void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) override;\n    void bind() override;\n    void unbind() override;\n    void setTexParameter(GLenum pname, GLint param) override;\n    bool ok() override;\n    bool isDMA() override;\n\n  private:\n    void* m_eglImage = nullptr;\n\n    enum eTextureParam : uint8_t {\n        TEXTURE_PAR_WRAP_S = 0,\n        TEXTURE_PAR_WRAP_T,\n        TEXTURE_PAR_MAG_FILTER,\n        TEXTURE_PAR_MIN_FILTER,\n        TEXTURE_PAR_SWIZZLE_R,\n        TEXTURE_PAR_SWIZZLE_B,\n        TEXTURE_PAR_LAST,\n    };\n\n    GLenum                                             m_target = GL_TEXTURE_2D;\n\n    void                                               swizzle(const std::array<GLint, 4>& colors);\n    constexpr std::optional<size_t>                    getCacheStateIndex(GLenum pname);\n\n    std::array<std::optional<GLint>, TEXTURE_PAR_LAST> m_cachedStates;\n};\n"
  },
  {
    "path": "src/render/pass/BorderPassElement.cpp",
    "content": "#include \"BorderPassElement.hpp\"\n\nCBorderPassElement::CBorderPassElement(const CBorderPassElement::SBorderData& data_) : m_data(data_) {\n    ;\n}\n\nbool CBorderPassElement::needsLiveBlur() {\n    return false;\n}\n\nbool CBorderPassElement::needsPrecomputeBlur() {\n    return false;\n}\n"
  },
  {
    "path": "src/render/pass/BorderPassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n#include \"../../config/ConfigDataValues.hpp\"\n\nclass CGradientValueData;\n\nclass CBorderPassElement : public IPassElement {\n  public:\n    struct SBorderData {\n        CBox               box;\n        CGradientValueData grad1, grad2;\n        bool               hasGrad2 = false;\n        float              lerp = 0.F, a = 1.F;\n        int                round = 0, borderSize = 1, outerRound = -1;\n        float              roundingPower = 2.F;\n        PHLWINDOWREF       window;\n    };\n\n    CBorderPassElement(const SBorderData& data_);\n    virtual ~CBorderPassElement() = default;\n\n    virtual bool        needsLiveBlur();\n    virtual bool        needsPrecomputeBlur();\n\n    virtual const char* passName() {\n        return \"CBorderPassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_BORDER;\n    };\n\n    SBorderData m_data;\n};\n"
  },
  {
    "path": "src/render/pass/ClearPassElement.cpp",
    "content": "#include \"ClearPassElement.hpp\"\n\nCClearPassElement::CClearPassElement(const CClearPassElement::SClearData& data_) : m_data(data_) {\n    ;\n}\n\nbool CClearPassElement::needsLiveBlur() {\n    return false;\n}\n\nbool CClearPassElement::needsPrecomputeBlur() {\n    return false;\n}\n\nstd::optional<CBox> CClearPassElement::boundingBox() {\n    return CBox{{}, {INT16_MAX, INT16_MAX}};\n}\n\nCRegion CClearPassElement::opaqueRegion() {\n    return *boundingBox();\n}\n"
  },
  {
    "path": "src/render/pass/ClearPassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n\nclass CClearPassElement : public IPassElement {\n  public:\n    struct SClearData {\n        CHyprColor color;\n    };\n\n    CClearPassElement(const SClearData& data);\n    virtual ~CClearPassElement() = default;\n\n    virtual bool                needsLiveBlur();\n    virtual bool                needsPrecomputeBlur();\n    virtual std::optional<CBox> boundingBox();\n    virtual CRegion             opaqueRegion();\n\n    virtual const char*         passName() {\n        return \"CClearPassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_CLEAR;\n    };\n\n    SClearData m_data;\n};\n"
  },
  {
    "path": "src/render/pass/FramebufferElement.cpp",
    "content": "#include \"FramebufferElement.hpp\"\n\nCFramebufferElement::CFramebufferElement(const CFramebufferElement::SFramebufferElementData& data_) : m_data(data_) {\n    ;\n}\n\nbool CFramebufferElement::needsLiveBlur() {\n    return false;\n}\n\nbool CFramebufferElement::needsPrecomputeBlur() {\n    return false;\n}\n\nbool CFramebufferElement::undiscardable() {\n    return true;\n}\n"
  },
  {
    "path": "src/render/pass/FramebufferElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n\nclass CFramebufferElement : public IPassElement {\n  public:\n    struct SFramebufferElementData {\n        bool    main          = true;\n        uint8_t framebufferID = 0;\n    };\n\n    CFramebufferElement(const SFramebufferElementData& data_);\n    virtual ~CFramebufferElement() = default;\n\n    virtual bool        needsLiveBlur();\n    virtual bool        needsPrecomputeBlur();\n    virtual bool        undiscardable();\n\n    virtual const char* passName() {\n        return \"CFramebufferElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_FRAMEBUFFER;\n    };\n\n    SFramebufferElementData m_data;\n};"
  },
  {
    "path": "src/render/pass/Pass.cpp",
    "content": "#include \"Pass.hpp\"\n#include \"../OpenGL.hpp\"\n#include <algorithm>\n#include <ranges>\n#include \"../../Compositor.hpp\"\n#include \"../../config/ConfigValue.hpp\"\n#include \"../../desktop/view/WLSurface.hpp\"\n#include \"../../managers/SeatManager.hpp\"\n#include \"../../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../../render/Renderer.hpp\"\n#include \"../../desktop/state/FocusState.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n\nbool CRenderPass::empty() const {\n    return false;\n}\n\nbool CRenderPass::single() const {\n    return m_passElements.size() == 1;\n}\n\nvoid CRenderPass::add(UP<IPassElement>&& el) {\n    m_passElements.emplace_back(makeUnique<SPassElementData>(CRegion{}, std::move(el)));\n}\n\nvoid CRenderPass::simplify() {\n    const auto  pMonitor   = g_pHyprRenderer->m_renderData.pMonitor;\n    static auto PDEBUGPASS = CConfigValue<Hyprlang::INT>(\"debug:pass\");\n\n    // TODO: use precompute blur for instances where there is nothing in between\n\n    // if there is live blur, we need to NOT occlude any area where it will be influenced\n    const auto WILLBLUR = std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->needsLiveBlur(); });\n\n    CRegion    newDamage = m_damage.copy().intersect(CBox{{}, pMonitor->m_transformedSize});\n    for (auto& el : m_passElements | std::views::reverse) {\n\n        if (newDamage.empty() && !el->element->undiscardable()) {\n            el->discard = true;\n            continue;\n        }\n\n        el->elementDamage = newDamage;\n        auto bb1          = el->element->boundingBox();\n        if (!bb1 || newDamage.empty())\n            continue;\n\n        auto bb = bb1->scale(pMonitor->m_scale);\n\n        // drop if empty\n        if (CRegion copy = newDamage.copy(); copy.intersect(bb).empty()) {\n            el->discard = true;\n            continue;\n        }\n\n        auto opaque = el->element->opaqueRegion();\n\n        if (!opaque.empty()) {\n            // scale and rounding is very particular so we have to use CBoxes scale and round functions\n            if (opaque.getRects().size() == 1)\n                opaque = opaque.getExtents().scale(pMonitor->m_scale).round();\n            else {\n                CRegion scaledRegion;\n                opaque.forEachRect([&scaledRegion, pMonitor](const auto& RECT) {\n                    scaledRegion.add(CBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1).scale(pMonitor->m_scale).round());\n                });\n                opaque = scaledRegion;\n            }\n\n            // if this intersects the liveBlur region, allow live blur to operate correctly.\n            // do not occlude a border near it.\n            if (WILLBLUR) {\n                CRegion liveBlurRegion;\n                for (auto& el2 : m_passElements) {\n                    // if we reach self, no problem, we can break.\n                    // if the blur is above us, we don't care, it will work fine.\n                    if (el2 == el)\n                        break;\n\n                    if (!el2->element->needsLiveBlur())\n                        continue;\n\n                    const auto BB = el2->element->boundingBox();\n                    RASSERT(BB, \"No bounding box for an element with live blur is illegal\");\n\n                    liveBlurRegion.add(*BB);\n                }\n\n                // expand the region: this area needs to be proper to blur it right.\n                liveBlurRegion.scale(pMonitor->m_scale).expand(oneBlurRadius() * 2.F);\n\n                if (auto infringement = opaque.copy().intersect(liveBlurRegion); !infringement.empty()) {\n                    // eh, this is not the correct solution, but it will do...\n                    // TODO: is this *easily* fixable?\n                    opaque.subtract(infringement);\n                }\n            }\n            newDamage.subtract(opaque);\n            if (*PDEBUGPASS)\n                m_occludedRegions.emplace_back(opaque);\n        }\n    }\n\n    if (*PDEBUGPASS) {\n        for (auto& el2 : m_passElements) {\n            if (!el2->element->needsLiveBlur())\n                continue;\n\n            const auto BB = el2->element->boundingBox();\n            RASSERT(BB, \"No bounding box for an element with live blur is illegal\");\n\n            m_totalLiveBlurRegion.add(BB->copy().scale(pMonitor->m_scale));\n        }\n    }\n}\n\nvoid CRenderPass::clear() {\n    m_passElements.clear();\n}\n\nCRegion CRenderPass::render(const CRegion& damage_) {\n    const auto  pMonitor   = g_pHyprRenderer->m_renderData.pMonitor;\n    static auto PDEBUGPASS = CConfigValue<Hyprlang::INT>(\"debug:pass\");\n\n    const auto  WILLBLUR = std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->needsLiveBlur(); });\n\n    m_damage = *PDEBUGPASS ? CRegion{CBox{{}, {INT32_MAX, INT32_MAX}}} : damage_.copy();\n    if (*PDEBUGPASS) {\n        m_occludedRegions.clear();\n        m_totalLiveBlurRegion = CRegion{};\n    }\n\n    if (m_damage.empty()) {\n        g_pHyprRenderer->m_renderData.damage      = m_damage;\n        g_pHyprRenderer->m_renderData.finalDamage = m_damage;\n        return m_damage;\n    }\n\n    if (!*PDEBUGPASS && m_debugData.present)\n        m_debugData = {false};\n    else if (*PDEBUGPASS && !m_debugData.present) {\n        m_debugData.present           = true;\n        m_debugData.keyboardFocusText = g_pHyprRenderer->renderText(\"keyboard\", Colors::WHITE, 12);\n        m_debugData.pointerFocusText  = g_pHyprRenderer->renderText(\"pointer\", Colors::WHITE, 12);\n        m_debugData.lastWindowText    = g_pHyprRenderer->renderText(\"lastWindow\", Colors::WHITE, 12);\n    }\n\n    if (WILLBLUR && !*PDEBUGPASS) {\n        // combine blur regions into one that will be expanded\n        CRegion blurRegion;\n        for (auto& el : m_passElements) {\n            if (!el->element->needsLiveBlur())\n                continue;\n\n            const auto BB = el->element->boundingBox();\n            RASSERT(BB, \"No bounding box for an element with live blur is illegal\");\n\n            blurRegion.add(*BB);\n        }\n\n        blurRegion.scale(pMonitor->m_scale);\n\n        blurRegion.intersect(m_damage).expand(oneBlurRadius());\n\n        g_pHyprRenderer->m_renderData.finalDamage = blurRegion.copy().add(m_damage);\n\n        // FIXME: why does this break on * 1.F ?\n        // used to work when we expand all the damage... I think? Well, before pass.\n        // moving a window over blur shows the edges being wonk.\n        blurRegion.expand(oneBlurRadius() * 1.5F);\n\n        m_damage = blurRegion.copy().add(m_damage);\n    } else\n        g_pHyprRenderer->m_renderData.finalDamage = m_damage;\n\n    if (g_pHyprRenderer->m_renderData.noSimplify || std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) {\n        for (auto& el : m_passElements) {\n            el->elementDamage = m_damage;\n        }\n    } else\n        simplify();\n\n    if (g_pHyprRenderer->m_renderData.pMonitor)\n        g_pHyprRenderer->m_renderData.pMonitor->m_blurFBShouldRender = std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->needsPrecomputeBlur(); });\n\n    if (m_passElements.empty())\n        return {};\n\n    for (auto& el : m_passElements) {\n        if (el->discard) {\n            el->element->discard();\n            continue;\n        }\n\n        g_pHyprRenderer->m_renderData.damage = el->elementDamage;\n        g_pHyprRenderer->draw(el->element, el->elementDamage);\n    }\n\n    if (*PDEBUGPASS) {\n        renderDebugData();\n        g_pEventLoopManager->doLater([] {\n            for (auto& m : g_pCompositor->m_monitors) {\n                g_pHyprRenderer->damageMonitor(m);\n            }\n        });\n    }\n\n    g_pHyprRenderer->m_renderData.damage = m_damage;\n    return m_damage;\n}\n\nvoid CRenderPass::renderDebugData() {\n    const auto pMonitor = g_pHyprRenderer->m_renderData.pMonitor;\n    CBox       box      = {{}, pMonitor->m_transformedSize};\n    for (const auto& rg : m_occludedRegions) {\n        CRectPassElement::SRectData data;\n        data.box   = box;\n        data.color = Colors::RED.modifyA(0.1F);\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(data), rg);\n    }\n    CRectPassElement::SRectData data;\n    data.box   = box;\n    data.color = Colors::GREEN.modifyA(0.1F);\n    g_pHyprRenderer->draw(makeUnique<CRectPassElement>(data), m_totalLiveBlurRegion);\n\n    std::unordered_map<CWLSurfaceResource*, float> offsets;\n\n    // render focus stuff\n    auto renderHLSurface = [&offsets, pMonitor](SP<ITexture> texture, SP<CWLSurfaceResource> surface, const CHyprColor& color) {\n        if (!surface || !texture)\n            return;\n\n        auto hlSurface = Desktop::View::CWLSurface::fromResource(surface);\n        if (!hlSurface)\n            return;\n\n        auto bb = hlSurface->getSurfaceBoxGlobal();\n\n        if (!bb.has_value())\n            return;\n\n        CBox box = bb->copy().translate(-pMonitor->m_position).scale(pMonitor->m_scale);\n\n        if (box.intersection(CBox{{}, pMonitor->m_size}).empty())\n            return;\n\n        static const auto           FULL_REGION = CRegion{0, 0, INT32_MAX, INT32_MAX};\n\n        CRectPassElement::SRectData data;\n        data.box   = box;\n        data.color = color;\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(data), FULL_REGION);\n\n        if (offsets.contains(surface.get()))\n            box.translate(Vector2D{0.F, offsets[surface.get()]});\n        else\n            offsets[surface.get()] = 0;\n\n        box = {box.pos(), texture->m_size};\n        CRectPassElement::SRectData data2;\n        data.box   = box;\n        data.color = color;\n        data.round = std::min(5.0, box.size().y);\n        g_pHyprRenderer->draw(makeUnique<CRectPassElement>(data2), FULL_REGION);\n\n        CTexPassElement::SRenderData texData;\n        texData.tex = texture;\n        texData.box = box;\n        g_pHyprRenderer->draw(makeUnique<CTexPassElement>(texData), {});\n\n        offsets[surface.get()] += texture->m_size.y;\n    };\n\n    renderHLSurface(m_debugData.keyboardFocusText, g_pSeatManager->m_state.keyboardFocus.lock(), Colors::PURPLE.modifyA(0.1F));\n    renderHLSurface(m_debugData.pointerFocusText, g_pSeatManager->m_state.pointerFocus.lock(), Colors::ORANGE.modifyA(0.1F));\n    if (Desktop::focusState()->window())\n        renderHLSurface(m_debugData.lastWindowText, Desktop::focusState()->window()->wlSurface()->resource(), Colors::LIGHT_BLUE.modifyA(0.1F));\n\n    if (g_pSeatManager->m_state.pointerFocus) {\n        if (g_pSeatManager->m_state.pointerFocus->m_current.input.intersect(CBox{{}, g_pSeatManager->m_state.pointerFocus->m_current.size}).getExtents().size() !=\n            g_pSeatManager->m_state.pointerFocus->m_current.size) {\n            auto hlSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock());\n            if (hlSurface) {\n                auto BOX = hlSurface->getSurfaceBoxGlobal();\n                if (BOX) {\n                    auto region = g_pSeatManager->m_state.pointerFocus->m_current.input.copy().scale(pMonitor->m_scale).translate(BOX->pos() - pMonitor->m_position);\n                    CRectPassElement::SRectData data;\n                    data.box   = box;\n                    data.color = CHyprColor{0.8F, 0.8F, 0.2F, 0.4F};\n                    g_pHyprRenderer->draw(makeUnique<CRectPassElement>(data), region);\n                }\n            }\n        }\n    }\n\n    const auto DISCARDED_ELEMENTS = std::ranges::count_if(m_passElements, [](const auto& e) { return e->discard; });\n    auto tex = g_pHyprRenderer->renderText(std::format(\"occlusion layers: {}\\npass elements: {} ({} discarded)\\nviewport: {:X0}\", m_occludedRegions.size(), m_passElements.size(),\n                                                       DISCARDED_ELEMENTS, pMonitor->m_pixelSize),\n                                           Colors::WHITE, 12);\n\n    if (tex) {\n        box = CBox{{0.F, pMonitor->m_size.y - tex->m_size.y}, tex->m_size}.scale(pMonitor->m_scale);\n        CTexPassElement::SRenderData texData;\n        texData.tex = tex;\n        texData.box = box;\n        g_pHyprRenderer->draw(makeUnique<CTexPassElement>(texData), {});\n    }\n\n    std::string passStructure;\n    auto        yn   = [](const bool val) -> const char* { return val ? \"yes\" : \"no\"; };\n    auto        tick = [](const bool val) -> const char* { return val ? \"✔\" : \"✖\"; };\n    for (const auto& el : m_passElements | std::views::reverse) {\n        passStructure += std::format(\"{} {} (bb: {} op: {}, pb: {}, lb: {})\\n\", tick(!el->discard), el->element->passName(), yn(el->element->boundingBox().has_value()),\n                                     yn(!el->element->opaqueRegion().empty()), yn(el->element->needsPrecomputeBlur()), yn(el->element->needsLiveBlur()));\n    }\n\n    if (!passStructure.empty())\n        passStructure.pop_back();\n\n    tex = g_pHyprRenderer->renderText(passStructure, Colors::WHITE, 12);\n    if (tex) {\n        box = CBox{{pMonitor->m_size.x - tex->m_size.x, pMonitor->m_size.y - tex->m_size.y}, tex->m_size}.scale(pMonitor->m_scale);\n        CTexPassElement::SRenderData texData;\n        texData.tex = tex;\n        texData.box = box;\n        g_pHyprRenderer->draw(makeUnique<CTexPassElement>(texData), {});\n    }\n}\n\nfloat CRenderPass::oneBlurRadius() {\n    // TODO: is this exact range correct?\n    static auto PBLURSIZE   = CConfigValue<Hyprlang::INT>(\"decoration:blur:size\");\n    static auto PBLURPASSES = CConfigValue<Hyprlang::INT>(\"decoration:blur:passes\");\n\n    const auto  BLUR_PASSES = std::clamp(*PBLURPASSES, sc<int64_t>(1), sc<int64_t>(8));\n\n    return std::clamp(*PBLURSIZE, sc<int64_t>(1), sc<int64_t>(40)) * pow(2, BLUR_PASSES); // is this 2^pass? I don't know but it works... I think.\n}\n\nvoid CRenderPass::removeAllOfType(const std::string& type) {\n    std::erase_if(m_passElements, [&type](const auto& e) { return e->element->passName() == type; });\n}\n"
  },
  {
    "path": "src/render/pass/Pass.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n#include \"PassElement.hpp\"\n\nclass CGradientValueData;\nclass ITexture;\n\nclass CRenderPass {\n  public:\n    bool    empty() const;\n    bool    single() const;\n\n    void    add(UP<IPassElement>&& elem);\n    void    clear();\n    void    removeAllOfType(const std::string& type);\n\n    CRegion render(const CRegion& damage_);\n\n  private:\n    CRegion              m_damage;\n    std::vector<CRegion> m_occludedRegions;\n    CRegion              m_totalLiveBlurRegion;\n\n    struct SPassElementData {\n        CRegion          elementDamage;\n        UP<IPassElement> element;\n        bool             discard = false;\n    };\n\n    std::vector<UP<SPassElementData>> m_passElements;\n\n    void                              simplify();\n    float                             oneBlurRadius();\n    void                              renderDebugData();\n\n    struct {\n        bool         present = false;\n        SP<ITexture> keyboardFocusText, pointerFocusText, lastWindowText;\n    } m_debugData;\n\n    friend class CHyprOpenGLImpl;\n};\n"
  },
  {
    "path": "src/render/pass/PassElement.cpp",
    "content": "#include \"PassElement.hpp\"\n\nstd::optional<CBox> IPassElement::boundingBox() {\n    return std::nullopt;\n}\n\nCRegion IPassElement::opaqueRegion() {\n    return {};\n}\n\nbool IPassElement::disableSimplification() {\n    return false;\n}\n\nvoid IPassElement::discard() {\n    ;\n}\n\nbool IPassElement::undiscardable() {\n    return false;\n}\n"
  },
  {
    "path": "src/render/pass/PassElement.hpp",
    "content": "#pragma once\n\n#include \"../../defines.hpp\"\n\nenum ePassElementType : uint8_t {\n    EK_UNKNOWN = 0,\n    EK_BORDER,\n    EK_CLEAR,\n    EK_FRAMEBUFFER,\n    EK_PRE_BLUR,\n    EK_RECT,\n    EK_HINTS,\n    EK_SHADOW,\n    EK_SURFACE,\n    EK_TEXTURE,\n    EK_TEXTURE_MATTE\n};\n\nclass IPassElement {\n  public:\n    virtual ~IPassElement() = default;\n\n    virtual bool                needsLiveBlur()       = 0;\n    virtual bool                needsPrecomputeBlur() = 0;\n    virtual const char*         passName()            = 0;\n    virtual ePassElementType    type()                = 0;\n    virtual void                discard();\n    virtual bool                undiscardable();\n    virtual std::optional<CBox> boundingBox();  // in monitor-local logical coordinates\n    virtual CRegion             opaqueRegion(); // in monitor-local logical coordinates\n    virtual bool                disableSimplification();\n};\n"
  },
  {
    "path": "src/render/pass/PreBlurElement.cpp",
    "content": "#include \"PreBlurElement.hpp\"\n\nCPreBlurElement::CPreBlurElement() = default;\n\nbool CPreBlurElement::needsLiveBlur() {\n    return false;\n}\n\nbool CPreBlurElement::needsPrecomputeBlur() {\n    return false;\n}\n\nbool CPreBlurElement::disableSimplification() {\n    return true;\n}\n\nbool CPreBlurElement::undiscardable() {\n    return true;\n}\n"
  },
  {
    "path": "src/render/pass/PreBlurElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n\nclass CPreBlurElement : public IPassElement {\n  public:\n    CPreBlurElement();\n    virtual ~CPreBlurElement() = default;\n\n    virtual bool        needsLiveBlur();\n    virtual bool        needsPrecomputeBlur();\n    virtual bool        disableSimplification();\n    virtual bool        undiscardable();\n\n    virtual const char* passName() {\n        return \"CPreBlurElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_PRE_BLUR;\n    };\n};"
  },
  {
    "path": "src/render/pass/RectPassElement.cpp",
    "content": "#include \"RectPassElement.hpp\"\n#include \"../Renderer.hpp\"\n\nCRectPassElement::CRectPassElement(const CRectPassElement::SRectData& data_) : m_data(data_) {\n    ;\n}\n\nbool CRectPassElement::needsLiveBlur() {\n    return m_data.color.a < 1.F && !m_data.xray && m_data.blur;\n}\n\nbool CRectPassElement::needsPrecomputeBlur() {\n    return m_data.color.a < 1.F && m_data.xray && m_data.blur;\n}\n\nstd::optional<CBox> CRectPassElement::boundingBox() {\n    return m_data.box.copy().scale(1.F / g_pHyprRenderer->m_renderData.pMonitor->m_scale).round();\n}\n\nCRegion CRectPassElement::opaqueRegion() {\n    if (m_data.color.a < 1.F)\n        return CRegion{};\n\n    CRegion rg = boundingBox()->expand(-m_data.round);\n\n    if (!m_data.clipBox.empty())\n        rg.intersect(m_data.clipBox);\n\n    return rg;\n}\n"
  },
  {
    "path": "src/render/pass/RectPassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n#include <hyprutils/math/Region.hpp>\n\nclass CRectPassElement : public IPassElement {\n  public:\n    struct SRectData {\n        CBox       box;\n        CHyprColor color;\n        int        round         = 0;\n        float      roundingPower = 2.0f;\n        bool       blur = false, xray = false;\n        float      blurA = 1.F;\n        CBox       clipBox;\n\n        // internal\n        CBox    modifiedBox;\n        float   TOPLEFT[2];\n        float   FULLSIZE[2];\n        CRegion drawRegion;\n    };\n\n    CRectPassElement(const SRectData& data);\n    virtual ~CRectPassElement() = default;\n\n    virtual bool                needsLiveBlur();\n    virtual bool                needsPrecomputeBlur();\n    virtual std::optional<CBox> boundingBox();\n    virtual CRegion             opaqueRegion();\n\n    virtual const char*         passName() {\n        return \"CRectPassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_RECT;\n    };\n\n    SRectData m_data;\n};\n"
  },
  {
    "path": "src/render/pass/RendererHintsPassElement.cpp",
    "content": "#include \"RendererHintsPassElement.hpp\"\n\nCRendererHintsPassElement::CRendererHintsPassElement(const CRendererHintsPassElement::SData& data_) : m_data(data_) {\n    ;\n}\n\nbool CRendererHintsPassElement::needsLiveBlur() {\n    return false;\n}\n\nbool CRendererHintsPassElement::needsPrecomputeBlur() {\n    return false;\n}\n\nbool CRendererHintsPassElement::undiscardable() {\n    return true;\n}\n"
  },
  {
    "path": "src/render/pass/RendererHintsPassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n#include <optional>\n#include \"../OpenGL.hpp\"\n\nclass CRendererHintsPassElement : public IPassElement {\n  public:\n    struct SData {\n        std::optional<SRenderModifData> renderModif;\n    };\n\n    CRendererHintsPassElement(const SData& data);\n    virtual ~CRendererHintsPassElement() = default;\n\n    virtual bool        needsLiveBlur();\n    virtual bool        needsPrecomputeBlur();\n    virtual bool        undiscardable();\n\n    virtual const char* passName() {\n        return \"CRendererHintsPassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_HINTS;\n    };\n\n    SData m_data;\n};"
  },
  {
    "path": "src/render/pass/ShadowPassElement.cpp",
    "content": "#include \"ShadowPassElement.hpp\"\n\nCShadowPassElement::CShadowPassElement(const CShadowPassElement::SShadowData& data_) : m_data(data_) {\n    ;\n}\n\nbool CShadowPassElement::needsLiveBlur() {\n    return false;\n}\n\nbool CShadowPassElement::needsPrecomputeBlur() {\n    return false;\n}\n"
  },
  {
    "path": "src/render/pass/ShadowPassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n\nclass CHyprDropShadowDecoration;\n\nclass CShadowPassElement : public IPassElement {\n  public:\n    struct SShadowData {\n        CHyprDropShadowDecoration* deco = nullptr;\n        float                      a    = 1.F;\n    };\n\n    CShadowPassElement(const SShadowData& data_);\n    virtual ~CShadowPassElement() = default;\n\n    virtual bool        needsLiveBlur();\n    virtual bool        needsPrecomputeBlur();\n\n    virtual const char* passName() {\n        return \"CShadowPassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_SHADOW;\n    };\n\n    SShadowData m_data;\n};\n"
  },
  {
    "path": "src/render/pass/SurfacePassElement.cpp",
    "content": "#include \"SurfacePassElement.hpp\"\n#include \"../OpenGL.hpp\"\n#include \"../../desktop/view/WLSurface.hpp\"\n#include \"../../desktop/view/Window.hpp\"\n#include \"../../protocols/core/Compositor.hpp\"\n#include \"../../protocols/DRMSyncobj.hpp\"\n#include \"../../managers/input/InputManager.hpp\"\n#include \"../../layout/LayoutManager.hpp\"\n#include \"../Renderer.hpp\"\n\n#include <hyprutils/math/Box.hpp>\n#include <hyprutils/math/Vector2D.hpp>\n#include <hyprutils/utils/ScopeGuard.hpp>\nusing namespace Hyprutils::Utils;\n\nCSurfacePassElement::CSurfacePassElement(const CSurfacePassElement::SRenderData& data_) : m_data(data_) {\n    ;\n}\n\nCBox CSurfacePassElement::getTexBox() {\n    const double outputX = -m_data.pMonitor->m_position.x, outputY = -m_data.pMonitor->m_position.y;\n\n    const auto   INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE;\n    auto         PSURFACE                    = Desktop::View::CWLSurface::fromResource(m_data.surface);\n\n    CBox         windowBox;\n    if (m_data.surface && m_data.mainSurface) {\n        windowBox = {sc<int>(outputX) + m_data.pos.x + m_data.localPos.x, sc<int>(outputY) + m_data.pos.y + m_data.localPos.y, m_data.w, m_data.h};\n\n        // however, if surface buffer w / h < box, we need to adjust them\n        const auto PWINDOW = PSURFACE ? Desktop::View::CWindow::fromView(PSURFACE->view()) : nullptr;\n\n        // center the surface if it's smaller than the viewport we assign it\n        if (PSURFACE && !PSURFACE->m_fillIgnoreSmall && PSURFACE->small() /* guarantees PWINDOW */) {\n            const auto CORRECT  = PSURFACE->correctSmallVec();\n            const auto SIZE     = PSURFACE->getViewporterCorrectedSize();\n            const auto REPORTED = PWINDOW->getReportedSize();\n\n            if (!INTERACTIVERESIZEINPROGRESS) {\n                windowBox.translate(CORRECT);\n\n                windowBox.width  = SIZE.x * (PWINDOW->m_realSize->value().x / REPORTED.x);\n                windowBox.height = SIZE.y * (PWINDOW->m_realSize->value().y / REPORTED.y);\n            } else {\n                windowBox.width  = SIZE.x;\n                windowBox.height = SIZE.y;\n            }\n        }\n    } else { //  here we clamp to 2, these might be some tiny specks\n\n        const auto SURFSIZE = m_data.surface->m_current.size;\n\n        windowBox = {sc<int>(outputX) + m_data.pos.x + m_data.localPos.x, sc<int>(outputY) + m_data.pos.y + m_data.localPos.y, std::max(sc<float>(SURFSIZE.x), 2.F),\n                     std::max(sc<float>(SURFSIZE.y), 2.F)};\n        if (m_data.pWindow && m_data.pWindow->m_realSize->isBeingAnimated() && m_data.surface && !m_data.mainSurface && m_data.squishOversized /* subsurface */) {\n            // adjust subsurfaces to the window\n            const auto REPORTED = m_data.pWindow->getReportedSize();\n            if (REPORTED.x != 0 && REPORTED.y != 0) {\n                windowBox.width  = (windowBox.width / REPORTED.x) * m_data.pWindow->m_realSize->value().x;\n                windowBox.height = (windowBox.height / REPORTED.y) * m_data.pWindow->m_realSize->value().y;\n            }\n        }\n    }\n\n    if (m_data.squishOversized) {\n        if (m_data.localPos.x + windowBox.width > m_data.w)\n            windowBox.width = m_data.w - m_data.localPos.x;\n        if (m_data.localPos.y + windowBox.height > m_data.h)\n            windowBox.height = m_data.h - m_data.localPos.y;\n    }\n\n    return windowBox;\n}\n\nbool CSurfacePassElement::needsLiveBlur() {\n    auto        PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);\n\n    const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier * PSURFACE->m_overallOpacity : 1.F);\n    const bool  BLUR  = m_data.blur && (!m_data.texture || !m_data.texture->m_opaque || ALPHA < 1.F);\n\n    if (!m_data.pLS && !m_data.pWindow)\n        return BLUR;\n\n    if (m_data.popup)\n        return BLUR;\n\n    const bool NEWOPTIM = g_pHyprRenderer->shouldUseNewBlurOptimizations(m_data.pLS, m_data.pWindow);\n\n    return BLUR && !NEWOPTIM;\n}\n\nbool CSurfacePassElement::needsPrecomputeBlur() {\n    auto        PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);\n\n    const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier * PSURFACE->m_overallOpacity : 1.F);\n    const bool  BLUR  = m_data.blur && (!m_data.texture || !m_data.texture->m_opaque || ALPHA < 1.F);\n\n    if (!m_data.pLS && !m_data.pWindow)\n        return BLUR;\n\n    if (m_data.popup)\n        return false;\n\n    const bool NEWOPTIM = g_pHyprRenderer->shouldUseNewBlurOptimizations(m_data.pLS, m_data.pWindow);\n\n    return BLUR && NEWOPTIM;\n}\n\nstd::optional<CBox> CSurfacePassElement::boundingBox() {\n    return getTexBox();\n}\n\nCRegion CSurfacePassElement::opaqueRegion() {\n    auto        PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);\n\n    const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier * PSURFACE->m_overallOpacity : 1.F);\n\n    if (ALPHA < 1.F)\n        return {};\n\n    if (m_data.surface && m_data.surface->m_current.size == Vector2D{m_data.w, m_data.h}) {\n        CRegion    opaqueSurf = m_data.surface->m_current.opaque.copy().intersect(CBox{{}, {m_data.w, m_data.h}});\n        const auto texBox     = getTexBox();\n        opaqueSurf.scale(texBox.size() / Vector2D{m_data.w, m_data.h});\n        return opaqueSurf.translate(m_data.pos + m_data.localPos - m_data.pMonitor->m_position).expand(-m_data.rounding);\n    }\n\n    return m_data.texture && m_data.texture->m_opaque ? boundingBox()->expand(-m_data.rounding) : CRegion{};\n}\n\nCRegion CSurfacePassElement::visibleRegion(bool& cancel) {\n    auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);\n    if (!PSURFACE)\n        return {};\n\n    const auto& bufferSize = m_data.surface->m_current.bufferSize;\n\n    auto        visibleRegion = PSURFACE->m_visibleRegion.copy();\n    if (visibleRegion.empty())\n        return {};\n\n    visibleRegion.intersect(CBox(Vector2D(), bufferSize));\n\n    if (visibleRegion.empty()) {\n        cancel = true;\n        return visibleRegion;\n    }\n\n    // deal with any rounding errors that might come from scaling\n    visibleRegion.expand(1);\n\n    auto uvTL = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft;\n    auto uvBR = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight;\n\n    if (uvTL == Vector2D(-1, -1))\n        uvTL = Vector2D(0, 0);\n\n    if (uvBR == Vector2D(-1, -1))\n        uvBR = Vector2D(1, 1);\n\n    visibleRegion.translate(-uvTL * bufferSize);\n\n    auto texBox = getTexBox();\n    texBox.scale(m_data.pMonitor->m_scale);\n    texBox.round();\n\n    visibleRegion.scale((Vector2D(1, 1) / (uvBR - uvTL)) * (texBox.size() / bufferSize));\n    visibleRegion.translate((m_data.pos + m_data.localPos - m_data.pMonitor->m_position) * m_data.pMonitor->m_scale);\n\n    return visibleRegion;\n}\n\nvoid CSurfacePassElement::discard() {\n    if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) {\n        Log::logger->log(Log::TRACE, \"discard for invisible surface\");\n        m_data.surface->presentFeedback(m_data.when, m_data.pMonitor->m_self.lock(), true);\n    }\n}\n"
  },
  {
    "path": "src/render/pass/SurfacePassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n#include \"TexPassElement.hpp\"\n#include <optional>\n#include \"../../helpers/time/Time.hpp\"\n\nclass CWLSurfaceResource;\nclass ITexture;\nclass CSyncTimeline;\n\nclass CSurfacePassElement : public IPassElement {\n  public:\n    struct SRenderData {\n        PHLMONITORREF          pMonitor;\n        Time::steady_tp        when = Time::steadyNow();\n        Vector2D               pos, localPos;\n\n        void*                  data        = nullptr;\n        SP<CWLSurfaceResource> surface     = nullptr;\n        SP<ITexture>           texture     = nullptr;\n        bool                   mainSurface = true;\n        double                 w = 0, h = 0;\n        int                    rounding      = 0;\n        bool                   dontRound     = true;\n        float                  roundingPower = 2.0F;\n        bool                   decorate      = false;\n        float                  alpha = 1.F, fadeAlpha = 1.F;\n        bool                   blur                  = false;\n        bool                   blockBlurOptimization = false;\n\n        // only for windows, not popups\n        bool squishOversized = true;\n\n        // for calculating UV\n        PHLWINDOW pWindow;\n        PHLLS     pLS;\n\n        bool      popup = false;\n\n        // counts how many surfaces this pass has rendered\n        int      surfaceCounter = 0;\n\n        CBox     clipBox = {}; // scaled coordinates\n\n        uint32_t discardMode    = DISCARD_OPAQUE;\n        float    discardOpacity = 0.f;\n\n        bool     useNearestNeighbor = false;\n\n        bool     flipEndFrame = false;\n    };\n\n    CSurfacePassElement(const SRenderData& data);\n    virtual ~CSurfacePassElement() = default;\n\n    virtual bool                needsLiveBlur();\n    virtual bool                needsPrecomputeBlur();\n    virtual std::optional<CBox> boundingBox();\n    virtual CRegion             opaqueRegion();\n    virtual void                discard();\n    CRegion                     visibleRegion(bool& cancel);\n\n    virtual const char*         passName() {\n        return \"CSurfacePassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_SURFACE;\n    };\n\n    SRenderData m_data;\n\n    CBox        getTexBox();\n};\n"
  },
  {
    "path": "src/render/pass/TexPassElement.cpp",
    "content": "#include \"TexPassElement.hpp\"\n#include \"../Renderer.hpp\"\n\nCTexPassElement::CTexPassElement(const SRenderData& data) : m_data(data) {\n    ;\n}\n\nCTexPassElement::CTexPassElement(CTexPassElement::SRenderData&& data) : m_data(std::move(data)) {\n    ;\n}\n\nbool CTexPassElement::needsLiveBlur() {\n    return false; // TODO?\n}\n\nbool CTexPassElement::needsPrecomputeBlur() {\n    return false; // TODO?\n}\n\nstd::optional<CBox> CTexPassElement::boundingBox() {\n    return m_data.box.copy().scale(1.F / g_pHyprRenderer->m_renderData.pMonitor->m_scale).round();\n}\n\nCRegion CTexPassElement::opaqueRegion() {\n    return {}; // TODO:\n}\n\nvoid CTexPassElement::discard() {\n    ;\n}\n"
  },
  {
    "path": "src/render/pass/TexPassElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n#include <optional>\n\nclass CWLSurfaceResource;\nclass ITexture;\nclass CSyncTimeline;\n\nenum eDiscardMode : uint8_t {\n    DISCARD_OPAQUE = 1,\n    DISCARD_ALPHA  = 1 << 1\n};\n\nclass CTexPassElement : public IPassElement {\n  public:\n    struct SRenderData {\n        SP<ITexture>           tex;\n        CBox                   box;\n        float                  a        = 1.F;\n        float                  blurA    = 1.F;\n        float                  overallA = 1.F;\n        CRegion                damage;\n        int                    round               = 0;\n        float                  roundingPower       = 2.0f;\n        bool                   flipEndFrame        = false;\n        bool                   useMirrorProjection = false;\n        CBox                   clipBox;\n        bool                   blur = false;\n        std::optional<float>   ignoreAlpha;\n        std::optional<bool>    blockBlurOptimization;\n        bool                   cmBackToSRGB = false;\n        SP<CMonitor>           cmBackToSRGBSource;\n\n        bool                   discardActive = false;\n        bool                   allowCustomUV = false;\n        SP<CWLSurfaceResource> surface       = nullptr;\n\n        uint32_t               discardMode    = DISCARD_OPAQUE;\n        float                  discardOpacity = 0.f;\n\n        CRegion                clipRegion;\n        PHLLSREF               currentLS;\n\n        SP<ITexture>           blurredBG;\n    };\n\n    CTexPassElement(const SRenderData& data);\n    CTexPassElement(SRenderData&& data);\n    virtual ~CTexPassElement() = default;\n\n    virtual bool                needsLiveBlur();\n    virtual bool                needsPrecomputeBlur();\n    virtual std::optional<CBox> boundingBox();\n    virtual CRegion             opaqueRegion();\n    virtual void                discard();\n\n    virtual const char*         passName() {\n        return \"CTexPassElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_TEXTURE;\n    };\n\n    SRenderData m_data;\n};\n"
  },
  {
    "path": "src/render/pass/TextureMatteElement.cpp",
    "content": "#include \"TextureMatteElement.hpp\"\n\nCTextureMatteElement::CTextureMatteElement(const CTextureMatteElement::STextureMatteData& data_) : m_data(data_) {\n    ;\n}\n\nbool CTextureMatteElement::needsLiveBlur() {\n    return false;\n}\n\nbool CTextureMatteElement::needsPrecomputeBlur() {\n    return false;\n}"
  },
  {
    "path": "src/render/pass/TextureMatteElement.hpp",
    "content": "#pragma once\n#include \"PassElement.hpp\"\n#include \"../Framebuffer.hpp\"\n\nclass ITexture;\n\nclass CTextureMatteElement : public IPassElement {\n  public:\n    struct STextureMatteData {\n        CBox             box;\n        SP<ITexture>     tex;\n        SP<IFramebuffer> fb;\n        bool             disableTransformAndModify = false;\n    };\n\n    CTextureMatteElement(const STextureMatteData& data_);\n    virtual ~CTextureMatteElement() = default;\n\n    virtual bool        needsLiveBlur();\n    virtual bool        needsPrecomputeBlur();\n\n    virtual const char* passName() {\n        return \"CTextureMatteElement\";\n    }\n\n    virtual ePassElementType type() {\n        return EK_TEXTURE_MATTE;\n    };\n\n    STextureMatteData m_data;\n};"
  },
  {
    "path": "src/render/shaders/glsl/CM.glsl",
    "content": "#ifndef ALLOW_INCLUDES\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#endif\n#include \"cm_helpers.glsl\"\n\nuniform vec2  srcTFRange;\nuniform vec2  dstTFRange;\n\nuniform float srcRefLuminance;\nuniform mat3  convertMatrix;\n\n#if USE_ICC\nuniform highp sampler3D iccLut3D;\nuniform float           iccLutSize;\n#endif\n\n#if USE_SDR_MOD\nuniform float sdrSaturation;\nuniform float sdrBrightnessMultiplier;\n#endif\n\n#if USE_TONEMAP\nuniform float maxLuminance;\nuniform float dstMaxLuminance;\nuniform float dstRefLuminance;\n#endif\n"
  },
  {
    "path": "src/render/shaders/glsl/blur1.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\nprecision         highp float;\nuniform sampler2D tex;\n\nuniform float     radius;\nuniform vec2      halfpixel;\nuniform int       passes;\nuniform float     vibrancy;\nuniform float     vibrancy_darkness;\n\nin vec2           v_texcoord;\nlayout(location = 0) out vec4 fragColor;\n\n#include \"blur1.glsl\"\n\nvoid main() {\n    fragColor = blur1(v_texcoord, tex, radius, halfpixel, passes, vibrancy, vibrancy_darkness);\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blur1.glsl",
    "content": "// see http://alienryderflex.com/hsp.html\nconst float Pr = 0.299;\nconst float Pg = 0.587;\nconst float Pb = 0.114;\n\n// Y is \"v\" ( brightness ). X is \"s\" ( saturation )\n// see https://www.desmos.com/3d/a88652b9a4\n// Determines if high brightness or high saturation is more important\nconst float a = 0.93;\nconst float b = 0.11;\nconst float c = 0.66; //  Determines the smoothness of the transition of unboosted to boosted colors\n//\n\n// http://www.flong.com/archive/texts/code/shapers_circ/\nfloat doubleCircleSigmoid(float x, float a) {\n    a = clamp(a, 0.0, 1.0);\n\n    float y = .0;\n    if (x <= a) {\n        y = a - sqrt(a * a - x * x);\n    } else {\n        y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.));\n    }\n    return y;\n}\n\nvec3 rgb2hsl(vec3 col) {\n    float red   = col.r;\n    float green = col.g;\n    float blue  = col.b;\n\n    float minc  = min(col.r, min(col.g, col.b));\n    float maxc  = max(col.r, max(col.g, col.b));\n    float delta = maxc - minc;\n\n    float lum = (minc + maxc) * 0.5;\n    float sat = 0.0;\n    float hue = 0.0;\n\n    if (lum > 0.0 && lum < 1.0) {\n        float mul = (lum < 0.5) ? (lum) : (1.0 - lum);\n        sat       = delta / (mul * 2.0);\n    }\n\n    if (delta > 0.0) {\n        vec3 maxcVec = vec3(maxc);\n        vec3 masks   = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red)));\n        vec3 adds    = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta;\n\n        hue += dot(adds, masks);\n        hue /= 6.0;\n\n        if (hue < 0.0)\n            hue += 1.0;\n    }\n\n    return vec3(hue, sat, lum);\n}\n\nvec3 hsl2rgb(vec3 col) {\n    const float onethird = 1.0 / 3.0;\n    const float twothird = 2.0 / 3.0;\n    const float rcpsixth = 6.0;\n\n    float       hue = col.x;\n    float       sat = col.y;\n    float       lum = col.z;\n\n    vec3        xt = vec3(0.0);\n\n    if (hue < onethird) {\n        xt.r = rcpsixth * (onethird - hue);\n        xt.g = rcpsixth * hue;\n        xt.b = 0.0;\n    } else if (hue < twothird) {\n        xt.r = 0.0;\n        xt.g = rcpsixth * (twothird - hue);\n        xt.b = rcpsixth * (hue - onethird);\n    } else\n        xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue));\n\n    xt = min(xt, 1.0);\n\n    float sat2   = 2.0 * sat;\n    float satinv = 1.0 - sat;\n    float luminv = 1.0 - lum;\n    float lum2m1 = (2.0 * lum) - 1.0;\n    vec3  ct     = (sat2 * xt) + satinv;\n\n    vec3  rgb;\n    if (lum >= 0.5)\n        rgb = (luminv * ct) + lum2m1;\n    else\n        rgb = lum * ct;\n\n    return rgb;\n}\n\nvec4 blur1(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel, int passes, float vibrancy, float vibrancy_darkness) {\n    vec2 uv = v_texcoord * 2.0;\n\n    vec4 sum = texture(tex, uv) * 4.0;\n    sum += texture(tex, uv - halfpixel.xy * radius);\n    sum += texture(tex, uv + halfpixel.xy * radius);\n    sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius);\n    sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius);\n\n    vec4 color = sum / 8.0;\n\n    if (vibrancy == 0.0) {\n        return color;\n    } else {\n        // Invert it so that it correctly maps to the config setting\n        float vibrancy_darkness1 = 1.0 - vibrancy_darkness;\n\n        // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest.\n        vec3 hsl = rgb2hsl(color.rgb);\n        // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow\n        float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1);\n\n        float b1        = b * vibrancy_darkness1;\n        float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0;\n\n        float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0);\n\n        vec3  newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2]));\n\n        return vec4(newColor, color[3]);\n    }\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blur2.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\nprecision         highp float;\n\nuniform sampler2D tex;\nuniform float     radius;\nuniform vec2      halfpixel;\n\nin vec2           v_texcoord;\nlayout(location = 0) out vec4 fragColor;\n\n#include \"blur2.glsl\"\n\nvoid main() {\n    fragColor = blur2(v_texcoord, tex, radius, halfpixel);\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blur2.glsl",
    "content": "vec4 blur2(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel) {\n    vec2 uv = v_texcoord / 2.0;\n\n    vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius);\n\n    sum += texture(tex, uv + vec2(-halfpixel.x,  halfpixel.y) * radius) * 2.0;\n    sum += texture(tex, uv + vec2(0.0,           halfpixel.y * 2.0) * radius);\n    sum += texture(tex, uv + vec2(halfpixel.x,   halfpixel.y) * radius) * 2.0;\n    sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius);\n    sum += texture(tex, uv + vec2(halfpixel.x,  -halfpixel.y) * radius) * 2.0;\n    sum += texture(tex, uv + vec2(0.0,          -halfpixel.y * 2.0) * radius);\n    sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0;\n\n    return sum / 12.0;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blurFinish.glsl",
    "content": "float hash(vec2 p) {\n    vec3 p3 = fract(vec3(p.xyx) * 1689.1984);\n    p3 += dot(p3, p3.yzx + 33.33);\n    return fract((p3.x + p3.y) * p3.z);\n}\n\nvec4 blurFinish(vec4 pixColor, vec2 v_texcoord, float noise, float brightness) {\n    // noise\n    float noiseHash   = hash(v_texcoord);\n    float noiseAmount = noiseHash - 0.5;\n    pixColor.rgb += noiseAmount * noise;\n\n    // brightness\n    pixColor.rgb *= min(1.0, brightness);\n\n    return pixColor;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blurfinish.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\nprecision         highp float;\nin vec2           v_texcoord; // is in 0-1\nuniform sampler2D tex;\n\nuniform float     noise;\nuniform float     brightness;\n\n#include \"blurFinish.glsl\"\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    vec4 pixColor = texture(tex, v_texcoord);\n\n    fragColor = blurFinish(pixColor, v_texcoord, noise, brightness);\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blurprepare.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\n#include \"defines.h\"\n\nprecision         highp float;\nin vec2           v_texcoord; // is in 0-1\nuniform sampler2D tex;\n\nuniform float     contrast;\nuniform float     brightness;\n\nuniform int       sourceTF; // eTransferFunction\nuniform int       targetTF; // eTransferFunction\n\n#if USE_CM\nuniform vec2  srcTFRange;\nuniform vec2  dstTFRange;\n\nuniform float srcRefLuminance;\nuniform mat3  convertMatrix;\n\nuniform float sdrBrightnessMultiplier;\n#include \"cm_helpers.glsl\"\n#endif\n\n#include \"blurprepare.glsl\"\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    fragColor = fragColor = blurPrepare(texture(tex, v_texcoord), contrast, brightness\n#if USE_CM\n                                        ,\n                                        sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange, srcRefLuminance, sdrBrightnessMultiplier\n#endif\n    );\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/blurprepare.glsl",
    "content": "#ifndef ALLOW_INCLUDES\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#endif\n\n#include \"defines.h\"\n\n#if USE_CM\n#include \"cm_helpers.glsl\"\n#endif\n\n#include \"gain.glsl\"\n\nvec4 blurPrepare(vec4 pixColor, float contrast, float brightness\n#if USE_CM\n                 ,\n                 int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange, float srcRefLuminance, float sdrBrightnessMultiplier\n#endif\n) {\n#if USE_CM\n    if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) {\n        pixColor.rgb /= sdrBrightnessMultiplier;\n    }\n    pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF);\n    pixColor     = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance));\n    pixColor     = fromLinearNit(pixColor, targetTF, dstTFRange);\n#endif\n\n    // contrast\n    if (contrast != 1.0)\n        pixColor.rgb = gain(pixColor.rgb, contrast);\n\n    // brightness\n    pixColor.rgb *= max(1.0, brightness);\n\n    return pixColor;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/border.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\nprecision     highp float;\nin vec2       v_texcoord;\n\nuniform int   sourceTF; // eTransferFunction\nuniform int   targetTF; // eTransferFunction\nuniform mat3  targetPrimariesXYZ;\n\nuniform vec2  fullSizeUntransformed;\nuniform float radiusOuter;\nuniform float thick;\n\n// Gradients are in OkLabA!!!! {l, a, b, alpha}\nuniform vec4  gradient[10];\nuniform vec4  gradient2[10];\nuniform int   gradientLength;\nuniform int   gradient2Length;\nuniform float angle;\nuniform float angle2;\nuniform float gradientLerp;\nuniform float alpha;\n\nuniform float radius;\nuniform float roundingPower;\nuniform vec2  topLeft;\nuniform vec2  fullSize;\n#include \"rounding.glsl\"\n#include \"CM.glsl\"\n#include \"border.glsl\"\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    fragColor = getBorder(v_texcoord, alpha, fullSizeUntransformed, radiusOuter, thick, radius, roundingPower, topLeft, fullSize, gradientLength, gradient, angle, gradient2Length,\n                          gradient2, angle2, gradientLerp\n#if USE_CM\n                          ,\n                          sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange\n#if USE_ICC\n                          ,\n                          iccLut3D, iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n                          ,\n                          targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n                          ,\n                          maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance\n#endif\n#if USE_SDR_MOD\n                          ,\n                          sdrSaturation, sdrBrightnessMultiplier\n#endif\n#endif\n#endif\n    );\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/border.glsl",
    "content": "#ifndef ALLOW_INCLUDES\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#endif\n#include \"cm_helpers.glsl\"\n#if USE_ROUNDING\n#include \"rounding.glsl\"\n#endif\n\nvec4 okLabAToSrgb(vec4 lab) {\n    float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0);\n    float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0);\n    float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0);\n\n    return vec4(fromLinearRGB(vec3(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965),\n                                   l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010),\n                              CM_TRANSFER_FUNCTION_GAMMA22),\n                lab[3]);\n}\n\nvec4 getOkColorForCoordArray1(vec2 normalizedCoord, int gradientLength, vec4 gradient[10], float angle) {\n    if (gradientLength < 2)\n        return gradient[0];\n\n    float finalAng = 0.0;\n\n    if (angle > 4.71 /* 270 deg */) {\n        normalizedCoord[1] = 1.0 - normalizedCoord[1];\n        finalAng           = 6.28 - angle;\n    } else if (angle > 3.14 /* 180 deg */) {\n        normalizedCoord[0] = 1.0 - normalizedCoord[0];\n        normalizedCoord[1] = 1.0 - normalizedCoord[1];\n        finalAng           = angle - 3.14;\n    } else if (angle > 1.57 /* 90 deg */) {\n        normalizedCoord[0] = 1.0 - normalizedCoord[0];\n        finalAng           = 3.14 - angle;\n    } else {\n        finalAng = angle;\n    }\n\n    float sine = sin(finalAng);\n\n    float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1);\n    int   bottom   = int(floor(progress));\n    int   top      = bottom + 1;\n\n    return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress);\n}\n\nvec4 getOkColorForCoordArray2(vec2 normalizedCoord, float angle, int gradient2Length, vec4 gradient2[10], float angle2) {\n    if (gradient2Length < 2)\n        return gradient2[0];\n\n    float finalAng = 0.0;\n\n    if (angle2 > 4.71 /* 270 deg */) {\n        normalizedCoord[1] = 1.0 - normalizedCoord[1];\n        finalAng           = 6.28 - angle;\n    } else if (angle2 > 3.14 /* 180 deg */) {\n        normalizedCoord[0] = 1.0 - normalizedCoord[0];\n        normalizedCoord[1] = 1.0 - normalizedCoord[1];\n        finalAng           = angle - 3.14;\n    } else if (angle2 > 1.57 /* 90 deg */) {\n        normalizedCoord[0] = 1.0 - normalizedCoord[0];\n        finalAng           = 3.14 - angle2;\n    } else {\n        finalAng = angle2;\n    }\n\n    float sine = sin(finalAng);\n\n    float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1);\n    int   bottom   = int(floor(progress));\n    int   top      = bottom + 1;\n\n    return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress);\n}\n\nvec4 getColorForCoord(vec2 normalizedCoord, int gradientLength, vec4 gradient[10], float angle, int gradient2Length, vec4 gradient2[10], float angle2, float gradientLerp) {\n    vec4 result1 = getOkColorForCoordArray1(normalizedCoord, gradientLength, gradient, angle);\n\n    if (gradient2Length <= 0)\n        return okLabAToSrgb(result1);\n\n    vec4 result2 = getOkColorForCoordArray2(normalizedCoord, angle, gradient2Length, gradient2, angle2);\n\n    return okLabAToSrgb(mix(result1, result2, gradientLerp));\n}\n\nvec4 getBorder(vec2 v_texcoord, float alpha, vec2 fullSizeUntransformed, float radiusOuter, float thick, float radius, float roundingPower, vec2 topLeft, vec2 fullSize,\n               int gradientLength, vec4 gradient[10], float angle, int gradient2Length, vec4 gradient2[10], float angle2, float gradientLerp\n#if USE_CM\n               ,\n               int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange\n#if USE_ICC\n               ,\n               highp sampler3D iccLut3D, float iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n               ,\n               mat3 targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n               ,\n               float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance\n#endif\n#if USE_SDR_MOD\n               ,\n               float sdrSaturation, float sdrBrightnessMultiplier\n#endif\n#endif\n#endif\n) {\n    vec2 pixCoord         = vec2(gl_FragCoord);\n    vec2 pixCoordOuter    = pixCoord;\n    vec2 originalPixCoord = v_texcoord;\n    originalPixCoord *= fullSizeUntransformed;\n    float additionalAlpha = 1.0;\n\n    vec4  pixColor = vec4(1.0, 1.0, 1.0, 1.0);\n\n    bool  done = false;\n\n    pixCoord -= topLeft + fullSize * 0.5;\n    pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0;\n    pixCoordOuter = pixCoord;\n    pixCoord -= fullSize * 0.5 - radius;\n    pixCoordOuter -= fullSize * 0.5 - radiusOuter;\n\n    // center the pixes don't make it top-left\n    pixCoord += vec2(1.0, 1.0) / fullSize;\n    pixCoordOuter += vec2(1.0, 1.0) / fullSize;\n\n#if USE_ROUNDING\n    if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) {\n        float dist      = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0 / roundingPower);\n        float distOuter = pow(pow(pixCoordOuter.x, roundingPower) + pow(pixCoordOuter.y, roundingPower), 1.0 / roundingPower);\n        float h         = (thick / 2.0);\n\n        if (dist < radius - h) {\n            // lower\n            float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));\n            additionalAlpha *= normalized;\n            done = true;\n        } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) {\n            // higher\n            float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));\n            additionalAlpha *= normalized;\n            done = true;\n        } else if (distOuter < radiusOuter - h) {\n            additionalAlpha = 1.0;\n            done            = true;\n        }\n    }\n#endif\n\n    // now check for other shit\n    if (!done) {\n        // distance to all straight bb borders\n        float distanceT = originalPixCoord[1];\n        float distanceB = fullSizeUntransformed[1] - originalPixCoord[1];\n        float distanceL = originalPixCoord[0];\n        float distanceR = fullSizeUntransformed[0] - originalPixCoord[0];\n\n        // get the smallest\n        float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR));\n\n        if (smallest > thick)\n            discard;\n    }\n\n    if (additionalAlpha == 0.0)\n        discard;\n\n    pixColor = getColorForCoord(v_texcoord, gradientLength, gradient, angle, gradient2Length, gradient2, angle2, gradientLerp);\n    pixColor.rgb *= pixColor[3];\n\n#if USE_CM\n    pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange\n#if USE_ICC\n                                 ,\n                                 iccLut3D, iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n                                 ,\n                                 targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n                                 ,\n                                 maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance\n#endif\n#if USE_SDR_MOD\n                                 ,\n                                 sdrSaturation, sdrBrightnessMultiplier\n#endif\n#endif\n    );\n#endif\n\n    pixColor *= alpha * additionalAlpha;\n\n    return pixColor;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/cm_helpers.glsl",
    "content": "#ifndef ALLOW_INCLUDES\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#endif\n#ifndef CM_HELPERS_GLSL\n#define CM_HELPERS_GLSL\n\n#include \"defines.h\"\n#include \"constants.h\"\n\n#if USE_SDR_MOD\nvec4 saturate(vec4 color, mat3 primaries, float saturation) {\n    if (saturation == 1.0)\n        return color;\n    vec3  brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]);\n    float Y          = dot(color.rgb, brightness);\n    return vec4(mix(vec3(Y), color.rgb, saturation), color[3]);\n}\n#endif\n\nvec3 applyIcc3DLut(vec3 linearRgb01, highp sampler3D iccLut3D, float iccLutSize) {\n    vec3 x = clamp(linearRgb01, 0.0, 1.0);\n\n    // Map [0..1] to texel centers to avoid edge issues\n    float N     = iccLutSize;\n    vec3  coord = (x * (N - 1.0) + 0.5) / N;\n\n    return texture(iccLut3D, coord).rgb;\n}\n\nvec3 xy2xyz(vec2 xy) {\n    if (xy.y == 0.0)\n        return vec3(0.0, 0.0, 0.0);\n\n    return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y);\n}\n\n// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf\nvec3 tfInvPQ(vec3 color) {\n    vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2));\n    return pow((max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), vec3(PQ_INV_M1));\n}\n\nvec3 tfInvHLG(vec3 color) {\n    bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT));\n    vec3  lo    = color.rgb * color.rgb / 3.0;\n    vec3  hi    = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0;\n    return mix(hi, lo, isLow);\n}\n\n// Many transfer functions (including sRGB) follow the same pattern: a linear\n// segment for small values and a power function for larger values. The\n// following function implements this pattern from which sRGB, BT.1886, and\n// others can be derived by plugging in the right constants.\nvec3 tfInvLinPow(vec3 color, float gamma, float thres, float scale, float alpha) {\n    bvec3 isLow = lessThanEqual(color.rgb, vec3(thres * scale));\n    vec3  lo    = color.rgb / scale;\n    vec3  hi    = pow((color.rgb + alpha - 1.0) / alpha, vec3(gamma));\n    return mix(hi, lo, isLow);\n}\n\nvec3 tfInvSRGB(vec3 color) {\n    return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA);\n}\n\nvec3 tfInvExtSRGB(vec3 color) {\n    // EXT sRGB is the sRGB transfer function mirrored around 0.\n    return sign(color) * tfInvSRGB(abs(color));\n}\n\nvec3 tfInvBT1886(vec3 color) {\n    return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA);\n}\n\nvec3 tfInvXVYCC(vec3 color) {\n    // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0,\n    // same as what EXT sRGB is to sRGB.\n    return sign(color) * tfInvBT1886(abs(color));\n}\n\nvec3 tfInvST240(vec3 color) {\n    return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA);\n}\n\n// Forward transfer functions corresponding to the inverse functions above.\nvec3 tfPQ(vec3 color) {\n    vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1));\n    return pow((vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), vec3(PQ_M2));\n}\n\nvec3 tfHLG(vec3 color) {\n    bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT));\n    vec3  lo    = sqrt(max(color.rgb, vec3(0.0)) * 3.0);\n    vec3  hi    = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C;\n    return mix(hi, lo, isLow);\n}\n\nvec3 tfLinPow(vec3 color, float gamma, float thres, float scale, float alpha) {\n    bvec3 isLow = lessThanEqual(color.rgb, vec3(thres));\n    vec3  lo    = color.rgb * scale;\n    vec3  hi    = pow(color.rgb, vec3(1.0 / gamma)) * alpha - (alpha - 1.0);\n    return mix(hi, lo, isLow);\n}\n\nvec3 tfSRGB(vec3 color) {\n    return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA);\n}\n\nvec3 tfExtSRGB(vec3 color) {\n    // EXT sRGB is the sRGB transfer function mirrored around 0.\n    return sign(color) * tfSRGB(abs(color));\n}\n\nvec3 tfBT1886(vec3 color) {\n    return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA);\n}\n\nvec3 tfXVYCC(vec3 color) {\n    // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0,\n    // same as what EXT sRGB is to sRGB.\n    return sign(color) * tfBT1886(abs(color));\n}\n\nvec3 tfST240(vec3 color) {\n    return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA);\n}\n\nvec3 toLinearRGB(vec3 color, int tf) {\n    switch (tf) {\n        case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color;\n        case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color);\n        case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(2.2));\n        case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(2.8));\n        case CM_TRANSFER_FUNCTION_HLG: return tfInvHLG(color);\n        case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfInvExtSRGB(color);\n        case CM_TRANSFER_FUNCTION_BT1886: return tfInvBT1886(color);\n        case CM_TRANSFER_FUNCTION_ST240: return tfInvST240(color);\n        case CM_TRANSFER_FUNCTION_LOG_100: return mix(exp((color - 1.0) * 2.0 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0)));\n        case CM_TRANSFER_FUNCTION_LOG_316: return mix(exp((color - 1.0) * 2.5 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0)));\n        case CM_TRANSFER_FUNCTION_XVYCC: return tfInvXVYCC(color);\n        case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)), vec3(ST428_POW)) * ST428_SCALE;\n        case CM_TRANSFER_FUNCTION_SRGB:\n        default: return tfInvSRGB(color);\n    }\n}\n\nvec4 toLinear(vec4 color, int tf) {\n    if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR)\n        return color;\n\n    color.rgb /= max(color.a, 0.001);\n    color.rgb = toLinearRGB(color.rgb, tf);\n    color.rgb *= color.a;\n    return color;\n}\n\nvec4 toNit(vec4 color, vec2 range) {\n    color.rgb = color.rgb * (range[1] - range[0]) + range[0];\n    return color;\n}\n\nvec3 fromLinearRGB(vec3 color, int tf) {\n    switch (tf) {\n        case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color;\n        case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfPQ(color);\n        case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.2));\n        case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.8));\n        case CM_TRANSFER_FUNCTION_HLG: return tfHLG(color);\n        case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfExtSRGB(color);\n        case CM_TRANSFER_FUNCTION_BT1886: return tfBT1886(color);\n        case CM_TRANSFER_FUNCTION_ST240: return tfST240(color);\n        case CM_TRANSFER_FUNCTION_LOG_100: return mix(1.0 + log(color) / log(10.0) / 2.0, vec3(0.0), lessThanEqual(color, vec3(0.01)));\n        case CM_TRANSFER_FUNCTION_LOG_316: return mix(1.0 + log(color) / log(10.0) / 2.5, vec3(0.0), lessThanEqual(color, vec3(sqrt(10.0) / 1000.0)));\n        case CM_TRANSFER_FUNCTION_XVYCC: return tfXVYCC(color);\n        case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)) / ST428_SCALE, vec3(1.0 / ST428_POW));\n        case CM_TRANSFER_FUNCTION_SRGB:\n        default: return tfSRGB(color);\n    }\n}\n\nvec4 fromLinear(vec4 color, int tf) {\n    if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR)\n        return color;\n\n    color.rgb /= max(color.a, 0.001);\n    color.rgb = fromLinearRGB(color.rgb, tf);\n    color.rgb *= color.a;\n    return color;\n}\n\nvec4 fromLinearNit(vec4 color, int tf, vec2 range) {\n    if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR)\n        color.rgb = color.rgb / SDR_MAX_LUMINANCE;\n    else {\n        color.rgb /= max(color.a, 0.001);\n        color.rgb = (color.rgb - range[0]) / (range[1] - range[0]);\n        color.rgb = fromLinearRGB(color.rgb, tf);\n        color.rgb *= color.a;\n    }\n    return color;\n}\n\n#if USE_TONEMAP\n#include \"tonemap.glsl\"\n#endif\n\nvec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange\n#if USE_ICC\n                       ,\n                       highp sampler3D iccLut3D, float iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n                       ,\n                       mat3 dstxyz\n#endif\n#if USE_TONEMAP\n                       ,\n                       float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance\n#endif\n#if USE_SDR_MOD\n                       ,\n                       float sdrSaturation, float sdrBrightnessMultiplier\n#endif\n#endif\n) {\n    pixColor.rgb /= max(pixColor.a, 0.001);\n    pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF);\n#if USE_ICC\n    pixColor.rgb = applyIcc3DLut(pixColor.rgb, iccLut3D, iccLutSize);\n    pixColor.rgb *= pixColor.a;\n#else\n    pixColor.rgb = convertMatrix * pixColor.rgb;\n    pixColor     = toNit(pixColor, srcTFRange);\n    pixColor.rgb *= pixColor.a;\n#if USE_TONEMAP\n    pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance);\n#endif\n    pixColor = fromLinearNit(pixColor, dstTF, dstTFRange);\n#if USE_SDR_MOD\n    pixColor = saturate(pixColor, dstxyz, sdrSaturation);\n    pixColor.rgb *= sdrBrightnessMultiplier;\n#endif\n#endif\n\n    return pixColor;\n}\n\n#endif"
  },
  {
    "path": "src/render/shaders/glsl/constants.h",
    "content": "#ifndef CONSTANTS_H\n#define CONSTANTS_H\n//enum eTransferFunction\n#define CM_TRANSFER_FUNCTION_BT1886     1\n#define CM_TRANSFER_FUNCTION_GAMMA22    2\n#define CM_TRANSFER_FUNCTION_GAMMA28    3\n#define CM_TRANSFER_FUNCTION_ST240      4\n#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5\n#define CM_TRANSFER_FUNCTION_LOG_100    6\n#define CM_TRANSFER_FUNCTION_LOG_316    7\n#define CM_TRANSFER_FUNCTION_XVYCC      8\n#define CM_TRANSFER_FUNCTION_SRGB       9\n#define CM_TRANSFER_FUNCTION_EXT_SRGB   10\n#define CM_TRANSFER_FUNCTION_ST2084_PQ  11\n#define CM_TRANSFER_FUNCTION_ST428      12\n#define CM_TRANSFER_FUNCTION_HLG        13\n\n// sRGB constants\n#define SRGB_POW   2.4\n#define SRGB_CUT   0.0031308\n#define SRGB_SCALE 12.92\n#define SRGB_ALPHA 1.055\n\n#define BT1886_POW   (1.0 / 0.45)\n#define BT1886_CUT   0.018053968510807\n#define BT1886_SCALE 4.5\n#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT)\n\n// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf\n#define ST240_POW   (1.0 / 0.45)\n#define ST240_CUT   0.0228\n#define ST240_SCALE 4.0\n#define ST240_ALPHA 1.1115\n\n#define ST428_POW   2.6\n#define ST428_SCALE (52.37 / 48.0)\n\n// PQ constants\n#define PQ_M1     0.1593017578125\n#define PQ_M2     78.84375\n#define PQ_INV_M1 (1.0 / PQ_M1)\n#define PQ_INV_M2 (1.0 / PQ_M2)\n#define PQ_C1     0.8359375\n#define PQ_C2     18.8515625\n#define PQ_C3     18.6875\n\n// HLG constants\n#define HLG_D_CUT (1.0 / 12.0)\n#define HLG_E_CUT 0.5\n#define HLG_A     0.17883277\n#define HLG_B     0.28466892\n#define HLG_C     0.55991073\n\n#define SDR_MIN_LUMINANCE 0.2\n#define SDR_MAX_LUMINANCE 80.0\n#define HDR_MIN_LUMINANCE 0.005\n#define HDR_MAX_LUMINANCE 10000.0\n#define HLG_MAX_LUMINANCE 1000.0\n\n#define M_E 2.718281828459045\n\n#endif\n"
  },
  {
    "path": "src/render/shaders/glsl/defines.h",
    "content": "// DO NOT EDIT. Will be overwritten in runtime\n#define USE_RGBA     1\n#define USE_DISCARD  1\n#define USE_TINT     1\n#define USE_ROUNDING 1\n#define USE_CM       1\n#define USE_TONEMAP  1\n#define USE_SDR_MOD  1\n#define USE_BLUR     1\n#define USE_ICC      1\n"
  },
  {
    "path": "src/render/shaders/glsl/ext.frag",
    "content": "#version 300 es\n\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#extension GL_OES_EGL_image_external_essl3 : require\n\nprecision                  highp float;\nin vec2                    v_texcoord;\nuniform samplerExternalOES tex;\nuniform float              alpha;\n\nuniform float              radius;\nuniform float              roundingPower;\nuniform vec2               topLeft;\nuniform vec2               fullSize;\n#include \"rounding.glsl\"\n\nuniform int  discardOpaque;\nuniform int  discardAlpha;\nuniform int  discardAlphaValue;\n\nuniform int  applyTint;\nuniform vec3 tint;\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n\n    vec4 pixColor = texture(tex, v_texcoord);\n\n    if (discardOpaque == 1 && pixColor[3] * alpha == 1.0)\n        discard;\n\n    if (applyTint == 1) {\n        pixColor[0] = pixColor[0] * tint[0];\n        pixColor[1] = pixColor[1] * tint[1];\n        pixColor[2] = pixColor[2] * tint[2];\n    }\n\n    if (radius > 0.0)\n        pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize);\n\n    fragColor = pixColor * alpha;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/gain.glsl",
    "content": "vec3 gain(vec3 x, float k) {\n    vec3 t = step(0.5, x);\n    vec3 y = mix(x, 1.0 - x, t);\n    vec3 a = 0.5 * pow(2.0 * y, vec3(k));\n    return mix(a, 1.0 - a, t);\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/glitch.frag",
    "content": "#version 300 es\n\nprecision highp float;\nin vec2 v_texcoord;\nuniform sampler2D tex;\nuniform float time; // quirk: time is set to 0 at the beginning, should be around 10 when crash.\nuniform float distort;\nuniform vec2 fullSize;\n\nfloat rand(float co) {\n    return fract(sin(dot(vec2(co, co), vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nfloat rand(vec2 co) {\n    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nfloat noise(vec2 point) {\n    vec2 floored = floor(point);\n    vec2 fractal = fract(point);\n    fractal = fractal * fractal * (3.0 - 2.0 * fractal);\n\n    float mixed = mix(\n        mix(rand(floored), rand(floored + vec2(1.0, 0.0)), fractal.x),\n        mix(rand(floored + vec2(0.0,1.0)), rand(floored + vec2(1.0,1.0)), fractal.x), fractal.y);\n    return mixed * mixed;\n}\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    float ABERR_OFFSET = 4.0 * (distort / 5.5) * time;\n    float TEAR_AMOUNT = 9000.0 * (1.0 - (distort / 5.5));\n    float TEAR_BANDS = 108.0 / 2.0 * (distort / 5.5) * 2.0;\n    float MELT_AMOUNT = (distort * 8.0) / fullSize.y;\n\n    float NOISE = abs(mod(noise(v_texcoord) * distort * time * 2.771, 1.0)) * time / 10.0;\n    if (time < 2.0)\n        NOISE = 0.0;\n\n    float offset = (mod(rand(floor(v_texcoord.y * TEAR_BANDS)) * 318.772 * time, 20.0) - 10.0) / TEAR_AMOUNT;\n\n    vec2 blockOffset = vec2(((abs(mod(rand(floor(v_texcoord.x * 37.162)) * 721.43, 100.0))) - 50.0) / 200000.0 * pow(time, 3.0),\n                            ((abs(mod(rand(floor(v_texcoord.y * 45.882)) * 733.923, 100.0))) - 50.0) / 200000.0 * pow(time, 3.0));\n    if (time < 3.0)\n        blockOffset = vec2(0,0);\n\n    float meltSeed = abs(mod(rand(floor(v_texcoord.x * fullSize.x * 17.719)) * 281.882, 1.0));\n    if (meltSeed < 0.8) {\n        meltSeed = 0.0;\n    } else {\n        meltSeed *= 25.0 * NOISE;\n    }\n    float meltAmount = MELT_AMOUNT * meltSeed;\n\n    vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / fullSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / fullSize.x + NOISE * 3.0 / fullSize.y  + blockOffset.y);\n\n    vec4 pixColor = texture(tex, pixCoord);\n    vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / fullSize.x, 0));\n    vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / fullSize.x, 0));\n\n    pixColor[0] = pixColorLeft[0];\n    pixColor[2] = pixColorRight[2];\n\n    pixColor[0] += distort / 90.0;\n\n    fragColor = pixColor;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/passthru.frag",
    "content": "#version 300 es\n\nprecision highp float;\nin vec2 v_texcoord; // is in 0-1\nuniform sampler2D tex;\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    fragColor = texture(tex, v_texcoord);\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/quad.frag",
    "content": "#version 300 es\n\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#include \"defines.h\"\n\nprecision highp float;\nin vec4   v_color;\n\n#if USE_ROUNDING\nuniform float radius;\nuniform float roundingPower;\nuniform vec2  topLeft;\nuniform vec2  fullSize;\n#include \"rounding.glsl\"\n#endif\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    vec4 pixColor = v_color;\n\n#if USE_ROUNDING\n    pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize);\n#endif\n\n    fragColor = pixColor;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/rgbamatte.frag",
    "content": "#version 300 es\n\nprecision highp float;\nin vec2 v_texcoord; // is in 0-1\nuniform sampler2D tex;\nuniform sampler2D texMatte;\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    fragColor = texture(tex, v_texcoord) * texture(texMatte, v_texcoord)[0]; // I know it only uses R, but matte should be black/white anyways.\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/rounding.glsl",
    "content": "#ifndef ROUNDING_GLSL\n#define ROUNDING_GLSL\n// smoothing constant for the edge: more = blurrier, but smoother\n#define M_PI               3.1415926535897932384626433832795\n#define SMOOTHING_CONSTANT (M_PI / 5.34665792551)\n\nvec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 fullSize) {\n    vec2 pixCoord = vec2(gl_FragCoord);\n    pixCoord -= topLeft + fullSize * 0.5;\n    pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0;\n    pixCoord -= fullSize * 0.5 - radius;\n    pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix don't make it top-left\n\n    if (pixCoord.x + pixCoord.y > radius) {\n        float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0 / roundingPower);\n\n        if (dist > radius + SMOOTHING_CONSTANT)\n            discard;\n\n        float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));\n\n        color *= normalized;\n    }\n\n    return color;\n}\n#endif"
  },
  {
    "path": "src/render/shaders/glsl/shadow.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\n#include \"defines.h\"\n\nprecision     highp float;\nin vec4       v_color;\nin vec2       v_texcoord;\n\nuniform int   sourceTF; // eTransferFunction\nuniform int   targetTF; // eTransferFunction\nuniform mat3  targetPrimariesXYZ;\n\nuniform vec2  topLeft;\nuniform vec2  bottomRight;\nuniform vec2  fullSize;\nuniform float radius;\nuniform float roundingPower;\nuniform float range;\nuniform float shadowPower;\n\n#if USE_CM\n#include \"cm_helpers.glsl\"\n#include \"CM.glsl\"\n#endif\n\n#include \"shadow.glsl\"\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n    vec4 pixColor = v_color;\n\n    fragColor = getShadow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight\n#if USE_CM\n                          ,\n                          sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange\n#if USE_ICC\n                          ,\n                          iccLut3D, iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n                          ,\n                          targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n                          ,\n                          maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance\n#endif\n#if USE_SDR_MOD\n                          ,\n                          sdrSaturation, sdrBrightnessMultiplier\n#endif\n#endif\n#endif\n    );\n}"
  },
  {
    "path": "src/render/shaders/glsl/shadow.glsl",
    "content": "#ifndef ALLOW_INCLUDES\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#endif\n#ifndef SHADOW_GLSL\n#define SHADOW_GLSL\n\n#include \"cm_helpers.glsl\"\n\nfloat pixAlphaRoundedDistance(float distanceToCorner, float radius, float range, float shadowPower) {\n    if (distanceToCorner > radius) {\n        return 0.0;\n    }\n\n    if (distanceToCorner > radius - range) {\n        return pow((range - (distanceToCorner - radius + range)) / range, shadowPower); // i think?\n    }\n\n    return 1.0;\n}\n\nfloat modifiedLength(vec2 a, float roundingPower) {\n    return pow(pow(abs(a.x), roundingPower) + pow(abs(a.y), roundingPower), 1.0 / roundingPower);\n}\n\nvec4 getShadow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight\n#if USE_CM\n               ,\n               int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange\n#if USE_ICC\n               ,\n               highp sampler3D iccLut3D, float iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n               ,\n               mat3 targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n               ,\n               float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance\n#endif\n#if USE_SDR_MOD\n               ,\n               float sdrSaturation, float sdrBrightnessMultiplier\n#endif\n#endif\n#endif\n) {\n    float originalAlpha = pixColor[3];\n\n    bool  done = false;\n\n    vec2  pixCoord = fullSize * v_texcoord;\n\n    // ok, now we check the distance to a border.\n\n    if (pixCoord[0] < topLeft[0]) {\n        if (pixCoord[1] < topLeft[1]) {\n            // top left\n            pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - topLeft, roundingPower), radius, range, shadowPower);\n            done        = true;\n        } else if (pixCoord[1] > bottomRight[1]) {\n            // bottom left\n            pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(topLeft[0], bottomRight[1]), roundingPower), radius, range, shadowPower);\n            done        = true;\n        }\n    } else if (pixCoord[0] > bottomRight[0]) {\n        if (pixCoord[1] < topLeft[1]) {\n            // top right\n            pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(bottomRight[0], topLeft[1]), roundingPower), radius, range, shadowPower);\n            done        = true;\n        } else if (pixCoord[1] > bottomRight[1]) {\n            // bottom right\n            pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - bottomRight, roundingPower), radius, range, shadowPower);\n            done        = true;\n        }\n    }\n\n    if (!done) {\n        // distance to all straight bb borders\n        float distanceT = pixCoord[1];\n        float distanceB = fullSize[1] - pixCoord[1];\n        float distanceL = pixCoord[0];\n        float distanceR = fullSize[0] - pixCoord[0];\n\n        // get the smallest\n        float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR));\n\n        if (smallest < range) {\n            pixColor[3] = pixColor[3] * pow((smallest / range), shadowPower);\n        }\n    }\n\n    if (pixColor[3] == 0.0) {\n        discard;\n        return pixColor;\n    }\n\n    // premultiply\n    pixColor.rgb *= pixColor[3];\n\n#if USE_CM\n    pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange\n#if USE_ICC\n                                 ,\n                                 iccLut3D, iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n                                 ,\n                                 targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n                                 ,\n                                 maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance\n#endif\n#if USE_SDR_MOD\n                                 ,\n                                 sdrSaturation, sdrBrightnessMultiplier\n#endif\n#endif\n    );\n#endif\n\n    return pixColor;\n}\n#endif"
  },
  {
    "path": "src/render/shaders/glsl/surface.frag",
    "content": "#version 300 es\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n\n#include \"defines.h\"\n\nprecision         highp float;\nin vec2           v_texcoord;\nuniform sampler2D tex;\n#if USE_BLUR\nuniform vec2      uvSize;\nuniform vec2      uvOffset;\nuniform sampler2D blurredBG;\n#endif\n\nuniform float alpha;\n\n#if USE_DISCARD\nuniform bool  discardOpaque;\nuniform bool  discardAlpha;\nuniform float discardAlphaValue;\n#endif\n\n#if USE_TINT\nuniform vec3 tint;\n#endif\n\n#if USE_ROUNDING\nuniform float radius;\nuniform float roundingPower;\nuniform vec2  topLeft;\nuniform vec2  fullSize;\n#include \"rounding.glsl\"\n#endif\n\n#if USE_CM\nuniform int sourceTF; // eTransferFunction\nuniform int targetTF; // eTransferFunction\n\n#if USE_TONEMAP || USE_SDR_MOD\nuniform mat3 targetPrimariesXYZ;\n#else\nconst mat3 targetPrimariesXYZ = mat3(0.0);\n#endif\n\n#include \"CM.glsl\"\n#endif\n\nlayout(location = 0) out vec4 fragColor;\nvoid main() {\n#if USE_RGBA\n    vec4 pixColor = texture(tex, v_texcoord);\n#else\n    vec4 pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0);\n#endif\n\n#if USE_DISCARD && !USE_BLUR\n    if (discardOpaque && pixColor.a * alpha == 1.0)\n        discard;\n\n    if (discardAlpha && pixColor.a <= discardAlphaValue)\n        discard;\n#endif\n\n#if USE_CM\n    pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange\n#if USE_ICC\n                                 ,\n                                 iccLut3D, iccLutSize\n#else\n#if USE_TONEMAP || USE_SDR_MOD\n                                 ,\n                                 targetPrimariesXYZ\n#endif\n#if USE_TONEMAP\n                                 ,\n                                 maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance\n#endif\n#if USE_SDR_MOD\n                                 ,\n                                 sdrSaturation, sdrBrightnessMultiplier\n#endif\n#endif\n    );\n#endif\n\n#if USE_TINT\n    pixColor.rgb = pixColor.rgb * tint;\n#endif\n\n#if USE_ROUNDING\n    pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize);\n#endif\n#if USE_BLUR\n#if USE_DISCARD\n    pixColor = mix(pixColor, vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, pixColor.rgb, pixColor.a), 1.0),\n                   discardAlpha && (pixColor.a <= discardAlphaValue) ? 0.0 : 1.0);\n#else\n    pixColor = vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, pixColor.rgb, pixColor.a), 1.0);\n#endif\n#endif\n\n    fragColor = pixColor * alpha;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/tex300.vert",
    "content": "#version 300 es\n\nuniform mat3 proj;\nuniform vec4 color;\n\nin vec2 pos;\nin vec2 texcoord;\nin vec2 texcoordMatte;\n\nout vec4 v_color;\nout vec2 v_texcoord;\nout vec2 v_texcoordMatte;\n\nvoid main() {\n    gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n    v_color = color;\n    v_texcoord = texcoord;\n    v_texcoordMatte = texcoordMatte;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/tex320.vert",
    "content": "#version 320 es\n\nuniform mat3 proj;\nuniform vec4 color;\n\nin vec2 pos;\nin vec2 texcoord;\nin vec2 texcoordMatte;\n\nout vec4 v_color;\nout vec2 v_texcoord;\nout vec2 v_texcoordMatte;\n\nvoid main() {\n    gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n    v_color = color;\n    v_texcoord = texcoord;\n    v_texcoordMatte = texcoordMatte;\n}\n"
  },
  {
    "path": "src/render/shaders/glsl/tonemap.glsl",
    "content": "#ifndef ALLOW_INCLUDES\n#define ALLOW_INCLUDES\n#extension GL_ARB_shading_language_include : enable\n#endif\n#include \"constants.h\"\n\nconst mat3 BT2020toLMS = mat3(0.3592, 0.6976, -0.0358, -0.1922, 1.1004, 0.0755, 0.0070, 0.0749, 0.8434);\n//const mat3 LMStoBT2020 = inverse(BT2020toLMS);\nconst mat3 LMStoBT2020 = mat3(                                                //\n    2.0701800566956135096, -1.3264568761030210255, 0.20661600684785517081,    //\n    0.36498825003265747974, 0.68046736285223514102, -0.045421753075853231409, //\n    -0.049595542238932107896, -0.049421161186757487412, 1.1879959417328034394 //\n);\n\n// const mat3 ICtCpPQ = transpose(mat3(\n//     2048.0, 2048.0, 0.0,\n//     6610.0, -13613.0, 7003.0,\n//     17933.0, -17390.0, -543.0\n// ) / 4096.0);\nconst mat3 ICtCpPQ = mat3(                //\n    0.5, 1.61376953125, 4.378173828125,   //\n    0.5, -3.323486328125, -4.24560546875, //\n    0.0, 1.709716796875, -0.132568359375  //\n);\n//const mat3 ICtCpPQInv = inverse(ICtCpPQ);\nconst mat3 ICtCpPQInv = mat3(                                                //\n    1.0, 1.0, 1.0,                                                           //\n    0.0086090370379327566, -0.0086090370379327566, 0.560031335710679118,     //\n    0.11102962500302595656, -0.11102962500302595656, -0.32062717498731885185 //\n);\n\n// unused for now\n// const mat3 ICtCpHLG = transpose(mat3(\n//     2048.0, 2048.0, 0.0,\n//     3625.0, -7465.0, 3840.0,\n//     9500.0, -9212.0, -288.0\n// ) / 4096.0);\n// const mat3 ICtCpHLGInv = inverse(ICtCpHLG);\n\nvec4 tonemap(vec4 color, mat3 dstXYZ, float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance) {\n    if (maxLuminance < dstMaxLuminance * 1.01)\n        return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]);\n\n    mat3  toLMS   = BT2020toLMS * dstXYZ;\n    mat3  fromLMS = inverse(dstXYZ) * LMStoBT2020;\n\n    vec3  lms   = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb;\n    vec3  ICtCp = ICtCpPQ * lms;\n\n    float E         = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2);\n    float luminance = pow((max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1) * HDR_MAX_LUMINANCE;\n\n    float linearPart         = min(luminance, dstRefLuminance);\n    float luminanceAboveRef  = max(luminance - dstRefLuminance, 0.0);\n    float maxExcessLuminance = max(maxLuminance - dstRefLuminance, 1.0);\n    float shoulder           = log((luminanceAboveRef / maxExcessLuminance + 1.0) * (M_E - 1.0));\n    float mappedHigh         = shoulder * (dstMaxLuminance - dstRefLuminance);\n    float newLum             = clamp(linearPart + mappedHigh, 0.0, dstMaxLuminance);\n\n    // scale src to dst reference\n    float refScale = dstRefLuminance / srcRefLuminance;\n\n    return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]);\n}\n"
  },
  {
    "path": "src/version.h.in",
    "content": "#pragma once\n#define GIT_COMMIT_HASH    \"@GIT_COMMIT_HASH@\"\n#define GIT_BRANCH         \"@GIT_BRANCH@\"\n#define GIT_COMMIT_MESSAGE \"@GIT_COMMIT_MESSAGE@\"\n#define GIT_COMMIT_DATE    \"@GIT_COMMIT_DATE@\"\n#define GIT_DIRTY          \"@GIT_DIRTY@\"\n#define GIT_TAG            \"@GIT_TAG@\"\n#define GIT_COMMITS        \"@GIT_COMMITS@\"\n\n#define AQUAMARINE_VERSION \"@AQUAMARINE_VERSION@\"\n// clang-format off\n#define AQUAMARINE_VERSION_MAJOR @AQUAMARINE_VERSION_MAJOR@\n#define AQUAMARINE_VERSION_MINOR @AQUAMARINE_VERSION_MINOR@\n#define AQUAMARINE_VERSION_PATCH @AQUAMARINE_VERSION_PATCH@\n// clang-format on\n#define HYPRLANG_VERSION     \"@HYPRLANG_VERSION@\"\n#define HYPRUTILS_VERSION    \"@HYPRUTILS_VERSION@\"\n#define HYPRCURSOR_VERSION   \"@HYPRCURSOR_VERSION@\"\n#define HYPRGRAPHICS_VERSION \"@HYPRGRAPHICS_VERSION@\"\n"
  },
  {
    "path": "src/xwayland/Dnd.cpp",
    "content": "#include \"Dnd.hpp\"\n#ifndef NO_XWAYLAND\n#include \"XWM.hpp\"\n#include \"XWayland.hpp\"\n#include \"Server.hpp\"\n#endif\n#include \"../managers/XWaylandManager.hpp\"\n#include \"../desktop/view/WLSurface.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n\nusing namespace Hyprutils::OS;\n\n#define PROPERTY_FORMAT_32BIT 32\n#define PROPERTY_LENGTH       1\n#define PROPERTY_OFFSET       0\n\n#ifndef NO_XWAYLAND\nstatic xcb_atom_t dndActionToAtom(uint32_t actions) {\n    if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)\n        return HYPRATOMS[\"XdndActionCopy\"];\n    else if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)\n        return HYPRATOMS[\"XdndActionMove\"];\n    else if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)\n        return HYPRATOMS[\"XdndActionAsk\"];\n\n    return XCB_ATOM_NONE;\n}\n\nvoid CX11DataDevice::sendDndEvent(xcb_window_t targetWindow, xcb_atom_t type, xcb_client_message_data_t& data) {\n    xcb_client_message_event_t event = {\n        .response_type = XCB_CLIENT_MESSAGE,\n        .format        = 32,\n        .sequence      = 0,\n        .window        = targetWindow,\n        .type          = type,\n        .data          = data,\n    };\n    xcb_connection_t* conn = (g_pXWayland->m_wm->getConnection());\n    xcb_send_event(conn, 0, targetWindow, XCB_EVENT_MASK_NO_EVENT, rc<const char*>(&event));\n    xcb_flush(conn);\n}\n\nxcb_window_t CX11DataDevice::getProxyWindow(xcb_window_t window) {\n    xcb_window_t              targetWindow = window;\n    xcb_get_property_cookie_t proxyCookie =\n        xcb_get_property((g_pXWayland->m_wm->getConnection()), PROPERTY_OFFSET, window, HYPRATOMS[\"XdndProxy\"], XCB_ATOM_WINDOW, PROPERTY_OFFSET, PROPERTY_LENGTH);\n    xcb_get_property_reply_t* proxyReply = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), proxyCookie, nullptr);\n\n    const auto                isValidPropertyReply = [](xcb_get_property_reply_t* reply) {\n        return reply && reply->type == XCB_ATOM_WINDOW && reply->format == PROPERTY_FORMAT_32BIT && xcb_get_property_value_length(reply) == sizeof(xcb_window_t);\n    };\n\n    if (isValidPropertyReply(proxyReply)) {\n        xcb_window_t              proxyWindow = *sc<xcb_window_t*>(xcb_get_property_value(proxyReply));\n\n        xcb_get_property_cookie_t proxyVerifyCookie =\n            xcb_get_property(g_pXWayland->m_wm->getConnection(), PROPERTY_OFFSET, proxyWindow, HYPRATOMS[\"XdndProxy\"], XCB_ATOM_WINDOW, PROPERTY_OFFSET, PROPERTY_LENGTH);\n        xcb_get_property_reply_t* proxyVerifyReply = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), proxyVerifyCookie, nullptr);\n\n        if (isValidPropertyReply(proxyVerifyReply)) {\n            xcb_window_t verifyWindow = *sc<xcb_window_t*>(xcb_get_property_value(proxyVerifyReply));\n            if (verifyWindow == proxyWindow) {\n                targetWindow = proxyWindow;\n                Log::logger->log(Log::DEBUG, \"Using XdndProxy window {:x} for window {:x}\", proxyWindow, window);\n            }\n        }\n        free(proxyVerifyReply); // NOLINT(cppcoreguidelines-no-malloc)\n    }\n    free(proxyReply); // NOLINT(cppcoreguidelines-no-malloc)\n\n    return targetWindow;\n}\n#endif\n\neDataSourceType CX11DataOffer::type() {\n    return DATA_SOURCE_TYPE_X11;\n}\n\nSP<CWLDataOfferResource> CX11DataOffer::getWayland() {\n    return nullptr;\n}\n\nSP<CX11DataOffer> CX11DataOffer::getX11() {\n    return m_self.lock();\n}\n\nSP<IDataSource> CX11DataOffer::getSource() {\n    return m_source.lock();\n}\n\nvoid CX11DataOffer::markDead() {\n#ifndef NO_XWAYLAND\n    std::erase(g_pXWayland->m_wm->m_dndDataOffers, m_self);\n#endif\n}\n\nvoid CX11DataDevice::sendDataOffer(SP<IDataOffer> offer) {\n    ; // no-op, I don't think this has an X equiv\n}\n\nvoid CX11DataDevice::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) {\n#ifndef NO_XWAYLAND\n    auto XSURF = g_pXWayland->m_wm->windowForWayland(surf);\n\n    if (!XSURF) {\n        Log::logger->log(Log::ERR, \"CX11DataDevice::sendEnter: No xwayland surface for destination\");\n        return;\n    }\n\n    auto SOURCE = offer->getSource();\n\n    if (!SOURCE) {\n        Log::logger->log(Log::ERR, \"CX11DataDevice::sendEnter: No source\");\n        return;\n    }\n\n    std::vector<xcb_atom_t> targets;\n    // reserve to avoid reallocations\n    targets.reserve(SOURCE->mimes().size());\n    for (auto const& m : SOURCE->mimes()) {\n        targets.push_back(g_pXWayland->m_wm->mimeToAtom(m));\n    }\n\n    xcb_change_property(g_pXWayland->m_wm->getConnection(), XCB_PROP_MODE_REPLACE, g_pXWayland->m_wm->m_dndSelection.window, HYPRATOMS[\"XdndTypeList\"], XCB_ATOM_ATOM, 32,\n                        targets.size(), targets.data());\n\n    xcb_set_selection_owner(g_pXWayland->m_wm->getConnection(), g_pXWayland->m_wm->m_dndSelection.window, HYPRATOMS[\"XdndSelection\"], XCB_TIME_CURRENT_TIME);\n    xcb_flush(g_pXWayland->m_wm->getConnection());\n\n    xcb_window_t              targetWindow = getProxyWindow(XSURF->m_xID);\n\n    xcb_client_message_data_t data = {{0}};\n    data.data32[0]                 = g_pXWayland->m_wm->m_dndSelection.window;\n    data.data32[1]                 = XDND_VERSION << 24;\n    data.data32[1] |= 1;\n\n    sendDndEvent(targetWindow, HYPRATOMS[\"XdndEnter\"], data);\n\n    m_lastSurface = XSURF;\n    m_lastOffer   = offer;\n\n    auto hlSurface = XSURF->m_surface.lock();\n    if (!hlSurface) {\n        Log::logger->log(Log::ERR, \"CX11DataDevice::sendEnter: Non desktop x surface?!\");\n        m_lastSurfaceCoords = {};\n        return;\n    }\n\n    m_lastSurfaceCoords = g_pXWaylandManager->xwaylandToWaylandCoords(XSURF->m_geometry.pos());\n#endif\n}\n\nvoid CX11DataDevice::cleanupState() {\n    m_lastSurface.reset();\n    m_lastOffer.reset();\n    m_lastSurfaceCoords = {};\n    m_lastTime          = 0;\n}\n\nvoid CX11DataDevice::sendLeave() {\n#ifndef NO_XWAYLAND\n    if (!m_lastSurface)\n        return;\n\n    xcb_window_t              targetWindow = getProxyWindow(m_lastSurface->m_xID);\n\n    xcb_client_message_data_t data = {{0}};\n    data.data32[0]                 = g_pXWayland->m_wm->m_dndSelection.window;\n\n    sendDndEvent(targetWindow, HYPRATOMS[\"XdndLeave\"], data);\n\n    cleanupState();\n#endif\n}\n\nvoid CX11DataDevice::sendMotion(uint32_t timeMs, const Vector2D& local) {\n#ifndef NO_XWAYLAND\n    if (!m_lastSurface || !m_lastOffer || !m_lastOffer->getSource())\n        return;\n\n    xcb_window_t              targetWindow = getProxyWindow(m_lastSurface->m_xID);\n\n    const auto                XCOORDS = g_pXWaylandManager->waylandToXWaylandCoords(m_lastSurfaceCoords + local);\n    const uint32_t            coords  = (sc<uint32_t>(XCOORDS.x) << 16) | sc<uint32_t>(XCOORDS.y);\n\n    xcb_client_message_data_t data = {{0}};\n    data.data32[0]                 = g_pXWayland->m_wm->m_dndSelection.window;\n    data.data32[2]                 = coords;\n    data.data32[3]                 = timeMs;\n    data.data32[4]                 = dndActionToAtom(m_lastOffer->getSource()->actions());\n\n    sendDndEvent(targetWindow, HYPRATOMS[\"XdndPosition\"], data);\n\n    m_lastTime = timeMs;\n#endif\n}\n\nvoid CX11DataDevice::sendDrop() {\n#ifndef NO_XWAYLAND\n    if (!m_lastSurface || !m_lastOffer) {\n        Log::logger->log(Log::ERR, \"CX11DataDevice::sendDrop: No surface or offer\");\n        return;\n    }\n\n    xcb_window_t              targetWindow = getProxyWindow(m_lastSurface->m_xID);\n\n    xcb_client_message_data_t data = {{0}};\n    data.data32[0]                 = g_pXWayland->m_wm->m_dndSelection.window;\n    data.data32[2]                 = m_lastTime;\n\n    sendDndEvent(targetWindow, HYPRATOMS[\"XdndDrop\"], data);\n\n    cleanupState();\n#endif\n}\n\nvoid CX11DataDevice::sendSelection(SP<IDataOffer> offer) {\n    ; // no-op. Selection is done separately.\n}\n\neDataSourceType CX11DataDevice::type() {\n    return DATA_SOURCE_TYPE_X11;\n}\n\nSP<CWLDataDeviceResource> CX11DataDevice::getWayland() {\n    return nullptr;\n}\n\nSP<CX11DataDevice> CX11DataDevice::getX11() {\n    return m_self.lock();\n}\n\nstd::vector<std::string> CX11DataSource::mimes() {\n    return m_mimeTypes;\n}\n\nvoid CX11DataSource::send(const std::string& mime, CFileDescriptor fd) {\n    ; // no-op\n}\n\nvoid CX11DataSource::accepted(const std::string& mime) {\n    ; // no-op\n}\n\nvoid CX11DataSource::cancelled() {\n    m_dndSuccess = false;\n    m_dropped    = false;\n}\n\nbool CX11DataSource::hasDnd() {\n    return m_dnd;\n}\n\nbool CX11DataSource::dndDone() {\n    return m_dropped;\n}\n\nvoid CX11DataSource::error(uint32_t code, const std::string& msg) {\n    Log::logger->log(Log::ERR, \"CX11DataSource error: code {} msg {}\", code, msg);\n    m_dndSuccess = false;\n    m_dropped    = false;\n}\n\nvoid CX11DataSource::sendDndFinished() {\n    m_dndSuccess = true;\n}\n\nuint32_t CX11DataSource::actions() {\n    return m_supportedActions;\n}\n\neDataSourceType CX11DataSource::type() {\n    return DATA_SOURCE_TYPE_X11;\n}\n\nvoid CX11DataSource::sendDndDropPerformed() {\n    m_dropped = true;\n}\n\nvoid CX11DataSource::sendDndAction(wl_data_device_manager_dnd_action a) {\n    ; // no-op\n}\n\nvoid CX11DataDevice::forceCleanupDnd() {\n#ifndef NO_XWAYLAND\n    if (m_lastOffer) {\n        auto source = m_lastOffer->getSource();\n        if (source) {\n            source->sendDndFinished();\n            source->cancelled();\n        }\n    }\n\n    xcb_set_selection_owner(g_pXWayland->m_wm->getConnection(), XCB_ATOM_NONE, HYPRATOMS[\"XdndSelection\"], XCB_TIME_CURRENT_TIME);\n    xcb_flush(g_pXWayland->m_wm->getConnection());\n\n    cleanupState();\n\n    g_pSeatManager->setPointerFocus(nullptr, {});\n    g_pInputManager->simulateMouseMovement();\n#endif\n}\n"
  },
  {
    "path": "src/xwayland/Dnd.hpp",
    "content": "#pragma once\n\n#include \"../protocols/types/DataDevice.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../managers/input/InputManager.hpp\"\n#include <wayland-server-protocol.h>\n#include <hyprutils/os/FileDescriptor.hpp>\n#ifndef NO_XWAYLAND\n#include <xcb/xcb.h>\n#endif\n\n#define XDND_VERSION 5\n\nclass CXWaylandSurface;\n\nclass CX11DataOffer : public IDataOffer {\n  public:\n    CX11DataOffer()  = default;\n    ~CX11DataOffer() = default;\n\n    virtual eDataSourceType          type();\n    virtual SP<CWLDataOfferResource> getWayland();\n    virtual SP<CX11DataOffer>        getX11();\n    virtual SP<IDataSource>          getSource();\n    virtual void                     markDead();\n\n    WP<IDataSource>                  m_source;\n    WP<CX11DataOffer>                m_self;\n    WP<CXWaylandSurface>             m_xwaylandSurface;\n};\n\nclass CX11DataSource : public IDataSource {\n  public:\n    CX11DataSource()  = default;\n    ~CX11DataSource() = default;\n\n    virtual std::vector<std::string> mimes();\n    virtual void                     send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd);\n    virtual void                     accepted(const std::string& mime);\n    virtual void                     cancelled();\n    virtual bool                     hasDnd();\n    virtual bool                     dndDone();\n    virtual void                     error(uint32_t code, const std::string& msg);\n    virtual void                     sendDndFinished();\n    virtual uint32_t                 actions(); // wl_data_device_manager.dnd_action\n    virtual eDataSourceType          type();\n    virtual void                     sendDndDropPerformed();\n    virtual void                     sendDndAction(wl_data_device_manager_dnd_action a);\n\n    bool                             m_dnd        = true;\n    bool                             m_dndSuccess = false;\n    bool                             m_dropped    = false;\n\n    std::vector<std::string>         m_mimeTypes;\n    uint32_t                         m_supportedActions = 0;\n};\n\nclass CX11DataDevice : public IDataDevice {\n  public:\n    CX11DataDevice() = default;\n\n    virtual SP<CWLDataDeviceResource> getWayland();\n    virtual SP<CX11DataDevice>        getX11();\n    virtual void                      sendDataOffer(SP<IDataOffer> offer);\n    virtual void                      sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer);\n    virtual void                      sendLeave();\n    virtual void                      sendMotion(uint32_t timeMs, const Vector2D& local);\n    virtual void                      sendDrop();\n    virtual void                      sendSelection(SP<IDataOffer> offer);\n    virtual eDataSourceType           type();\n    void                              forceCleanupDnd();\n\n    WP<CX11DataDevice>                m_self;\n\n  private:\n    void cleanupState();\n#ifndef NO_XWAYLAND\n    xcb_window_t getProxyWindow(xcb_window_t window);\n    void         sendDndEvent(xcb_window_t targetWindow, xcb_atom_t type, xcb_client_message_data_t& data);\n#endif\n    WP<CXWaylandSurface> m_lastSurface;\n    WP<IDataOffer>       m_lastOffer;\n    Vector2D             m_lastSurfaceCoords;\n    uint32_t             m_lastTime = 0;\n};\n"
  },
  {
    "path": "src/xwayland/Server.cpp",
    "content": "#include <cstdint>\n#ifndef NO_XWAYLAND\n\n#include <format>\n#include <string>\n#include <cerrno>\n#include <fcntl.h>\n#include <cstdio>\n#include <cstring>\n#include <csignal>\n#include <cstddef>\n#include <cstdlib>\n#include <sys/un.h>\n#include <unistd.h>\n#include <exception>\n#include <filesystem>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n\n#include \"Server.hpp\"\n#include \"XWayland.hpp\"\n#include \"config/ConfigValue.hpp\"\n#include \"debug/log/Logger.hpp\"\n#include \"../defines.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../managers/CursorManager.hpp\"\nusing namespace Hyprutils::OS;\n\n// Constants\nconstexpr int          SOCKET_DIR_PERMISSIONS = 0755;\nconstexpr int          SOCKET_BACKLOG         = 1;\nconstexpr int          MAX_SOCKET_RETRIES     = 32;\nconstexpr int          LOCK_FILE_MODE         = 0444;\n\nstatic CFileDescriptor createSocket(struct sockaddr_un* addr, size_t pathSize) {\n    const bool        isRegularSocket(addr->sun_path[0]);\n    const char        dbgSocketPathPrefix = isRegularSocket ? addr->sun_path[0] : '@';\n    const char* const dbgSocketPathRem    = addr->sun_path + 1;\n\n    socklen_t         size = offsetof(struct sockaddr_un, sun_path) + pathSize + 1;\n    CFileDescriptor   fd{socket(AF_UNIX, SOCK_STREAM, 0)};\n    if (!fd.isValid()) {\n        Log::logger->log(Log::ERR, \"Failed to create socket {}{}\", dbgSocketPathPrefix, dbgSocketPathRem);\n        return {};\n    }\n\n    if (!fd.setFlags(fd.getFlags() | FD_CLOEXEC)) {\n        Log::logger->log(Log::ERR, \"Failed to set flags for socket {}{}\", dbgSocketPathPrefix, dbgSocketPathRem);\n        return {};\n    }\n\n    if (isRegularSocket)\n        unlink(addr->sun_path);\n\n    if (bind(fd.get(), rc<struct sockaddr*>(addr), size) < 0) {\n        Log::logger->log(Log::ERR, \"Failed to bind socket {}{}\", dbgSocketPathPrefix, dbgSocketPathRem);\n        if (isRegularSocket)\n            unlink(addr->sun_path);\n        return {};\n    }\n\n    // Required for the correct functioning of `xhost` #9574\n    // The operation is safe because XWayland controls socket access by itself\n    // and rejects connections not matched by the `xhost` ACL\n    if (isRegularSocket && chmod(addr->sun_path, 0666) < 0) {\n        // We are only extending the default permissions,\n        // and I don't see the reason to make a full stop in case of a failed operation.\n        Log::logger->log(Log::ERR, \"Failed to set permission mode for socket {}{}\", dbgSocketPathPrefix, dbgSocketPathRem);\n    }\n\n    if (listen(fd.get(), SOCKET_BACKLOG) < 0) {\n        Log::logger->log(Log::ERR, \"Failed to listen to socket {}{}\", dbgSocketPathPrefix, dbgSocketPathRem);\n        if (isRegularSocket)\n            unlink(addr->sun_path);\n        return {};\n    }\n\n    return fd;\n}\n\nstatic bool checkPermissionsForSocketDir() {\n    struct stat buf;\n\n    if (lstat(\"/tmp/.X11-unix\", &buf)) {\n        Log::logger->log(Log::ERR, \"Failed to stat X11 socket dir\");\n        return false;\n    }\n\n    if (!(buf.st_mode & S_IFDIR)) {\n        Log::logger->log(Log::ERR, \"X11 socket dir is not a directory\");\n        return false;\n    }\n\n    if ((buf.st_uid != 0) && (buf.st_uid != getuid())) {\n        Log::logger->log(Log::ERR, \"X11 socket dir is not owned by root or current user\");\n        return false;\n    }\n\n    if (!(buf.st_mode & S_ISVTX)) {\n        if ((buf.st_mode & (S_IWGRP | S_IWOTH))) {\n            Log::logger->log(Log::ERR, \"X11 socket dir is writable by others\");\n            return false;\n        }\n    }\n\n    return true;\n}\n\nstatic bool ensureSocketDirExists() {\n    if (mkdir(\"/tmp/.X11-unix\", SOCKET_DIR_PERMISSIONS) != 0) {\n        if (errno == EEXIST)\n            return checkPermissionsForSocketDir();\n        else {\n            Log::logger->log(Log::ERR, \"XWayland: Couldn't create socket dir\");\n            return false;\n        }\n    }\n\n    return true;\n}\n\nstatic std::string getSocketPath(int display, bool isLinux) {\n    if (isLinux)\n        return std::format(\"/tmp/.X11-unix/X{}\", display);\n\n    return std::format(\"/tmp/.X11-unix/X{}_\", display);\n}\n\nstatic bool openSockets(std::array<CFileDescriptor, 2>& sockets, int display) {\n    static auto CREATEABSTRACTSOCKET = CConfigValue<Hyprlang::INT>(\"xwayland:create_abstract_socket\");\n\n    if (!ensureSocketDirExists())\n        return false;\n\n    sockaddr_un addr = {.sun_family = AF_UNIX};\n    std::string path;\n\n#ifdef __linux__\n    if (*CREATEABSTRACTSOCKET) {\n        // cursed...\n        // but is kept as an option for better compatibility\n        addr.sun_path[0] = 0;\n        path             = getSocketPath(display, true);\n        strncpy(addr.sun_path + 1, path.c_str(), path.length() + 1);\n    } else {\n        path = getSocketPath(display, false);\n        strncpy(addr.sun_path, path.c_str(), path.length() + 1);\n    }\n#else\n    if (*CREATEABSTRACTSOCKET) {\n        Log::logger->log(Log::WARN, \"The abstract XWayland Unix domain socket might be used only on Linux systems. A regular one'll be created instead.\");\n    }\n    path = getSocketPath(display, false);\n    strncpy(addr.sun_path, path.c_str(), path.length() + 1);\n#endif\n\n    sockets[0] = CFileDescriptor{createSocket(&addr, path.length())};\n    if (!sockets[0].isValid())\n        return false;\n\n    path = getSocketPath(display, true);\n    strncpy(addr.sun_path, path.c_str(), path.length() + 1);\n\n    sockets[1] = CFileDescriptor{createSocket(&addr, path.length())};\n    if (!sockets[1].isValid()) {\n        sockets[0].reset();\n        return false;\n    }\n\n    return true;\n}\n\nstatic void startServer(void* data) {\n    if (!g_pXWayland->m_server->start())\n        Log::logger->log(Log::ERR, \"The XWayland server could not start! XWayland will not work...\");\n}\n\nstatic int xwaylandReady(int fd, uint32_t mask, void* data) {\n    return g_pXWayland->m_server->ready(fd, mask);\n}\n\nstatic bool safeRemove(const std::string& path) {\n    try {\n        return std::filesystem::remove(path);\n    } catch (const std::exception& e) { Log::logger->log(Log::ERR, \"[XWayland] Failed to remove {}\", path); }\n    return false;\n}\n\nbool CXWaylandServer::tryOpenSockets() {\n    for (size_t i = 0; i <= MAX_SOCKET_RETRIES; ++i) {\n        std::string     lockPath = std::format(\"/tmp/.X{}-lock\", i);\n\n        CFileDescriptor fd{open(lockPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, LOCK_FILE_MODE)};\n        if (fd.isValid()) {\n            // we managed to open the lock\n            if (!openSockets(m_xFDs, i)) {\n                safeRemove(lockPath);\n                continue;\n            }\n\n            const std::string pidStr = std::format(\"{:010d}\\n\", getpid());\n            ASSERT(pidStr.length() == 11);\n            if (write(fd.get(), pidStr.c_str(), 11) != 11L) {\n                safeRemove(lockPath);\n                continue;\n            }\n\n            m_display     = i;\n            m_displayName = std::format(\":{}\", m_display);\n            break;\n        }\n\n        fd = CFileDescriptor{open(lockPath.c_str(), O_RDONLY | O_CLOEXEC)};\n\n        if (!fd.isValid())\n            continue;\n\n        char pidstr[12] = {0};\n        read(fd.get(), pidstr, sizeof(pidstr) - 1);\n\n        int32_t pid = 0;\n        try {\n            pid = std::stoi(std::string{pidstr, 11});\n        } catch (...) { continue; }\n\n        if (kill(pid, 0) != 0 && errno == ESRCH) {\n            if (!safeRemove(lockPath))\n                continue;\n            i--;\n        }\n    }\n\n    if (m_display < 0) {\n        Log::logger->log(Log::ERR, \"Failed to find a suitable socket for XWayland\");\n        return false;\n    }\n\n    Log::logger->log(Log::DEBUG, \"XWayland found a suitable display socket at DISPLAY: {}\", m_displayName);\n    return true;\n}\n\nCXWaylandServer::CXWaylandServer() {\n    ;\n}\n\nCXWaylandServer::~CXWaylandServer() {\n    die();\n    if (m_display < 0)\n        return;\n\n    std::string lockPath = std::format(\"/tmp/.X{}-lock\", m_display);\n    safeRemove(lockPath);\n\n    std::string path;\n    for (bool isLinux : {true, false}) {\n        path = getSocketPath(m_display, isLinux);\n        safeRemove(path);\n    }\n}\n\nvoid CXWaylandServer::die() {\n    if (m_display < 0)\n        return;\n\n    if (m_xFDReadEvents[0]) {\n        wl_event_source_remove(m_xFDReadEvents[0]);\n        wl_event_source_remove(m_xFDReadEvents[1]);\n        m_xFDReadEvents = {nullptr, nullptr};\n    }\n\n    if (m_pipeSource)\n        wl_event_source_remove(m_pipeSource);\n\n    // possible crash. Better to leak a bit.\n    //if (xwaylandClient)\n    //    wl_client_destroy(xwaylandClient);\n\n    m_xwaylandClient = nullptr;\n}\n\nbool CXWaylandServer::create() {\n    if (!tryOpenSockets())\n        return false;\n\n    setenv(\"DISPLAY\", m_displayName.c_str(), true);\n\n    // TODO: lazy mode\n\n    m_idleSource = wl_event_loop_add_idle(g_pCompositor->m_wlEventLoop, ::startServer, nullptr);\n\n    return true;\n}\n\nvoid CXWaylandServer::runXWayland(CFileDescriptor& notifyFD) {\n    if (!m_xFDs[0].setFlags(m_xFDs[0].getFlags() & ~FD_CLOEXEC) || !m_xFDs[1].setFlags(m_xFDs[1].getFlags() & ~FD_CLOEXEC) ||\n        !m_waylandFDs[1].setFlags(m_waylandFDs[1].getFlags() & ~FD_CLOEXEC) || !m_xwmFDs[1].setFlags(m_xwmFDs[1].getFlags() & ~FD_CLOEXEC)) {\n        Log::logger->log(Log::ERR, \"Failed to unset cloexec on fds\");\n        _exit(EXIT_FAILURE);\n    }\n\n    auto cmd = std::format(\"exec Xwayland {} -rootless -core -listenfd {} -listenfd {} -displayfd {} -wm {}\", m_displayName, m_xFDs[0].get(), m_xFDs[1].get(), notifyFD.get(),\n                           m_xwmFDs[1].get());\n\n    auto waylandSocket = std::format(\"{}\", m_waylandFDs[1].get());\n    setenv(\"WAYLAND_SOCKET\", waylandSocket.c_str(), true);\n\n    Log::logger->log(Log::DEBUG, \"Starting XWayland with \\\"{}\\\", bon voyage!\", cmd);\n\n    execl(\"/bin/sh\", \"/bin/sh\", \"-c\", cmd.c_str(), nullptr);\n\n    Log::logger->log(Log::ERR, \"XWayland failed to open\");\n    _exit(1);\n}\n\nbool CXWaylandServer::start() {\n    m_idleSource  = nullptr;\n    int wlPair[2] = {-1, -1};\n    if (socketpair(AF_UNIX, SOCK_STREAM, 0, wlPair) != 0) {\n        Log::logger->log(Log::ERR, \"socketpair failed (1)\");\n        die();\n        return false;\n    }\n    m_waylandFDs[0] = CFileDescriptor{wlPair[0]};\n    m_waylandFDs[1] = CFileDescriptor{wlPair[1]};\n\n    if (!m_waylandFDs[0].setFlags(m_waylandFDs[0].getFlags() | FD_CLOEXEC) || !m_waylandFDs[1].setFlags(m_waylandFDs[1].getFlags() | FD_CLOEXEC)) {\n        Log::logger->log(Log::ERR, \"set_cloexec failed (1)\");\n        die();\n        return false;\n    }\n\n    int xwmPair[2] = {-1, -1};\n    if (socketpair(AF_UNIX, SOCK_STREAM, 0, xwmPair) != 0) {\n        Log::logger->log(Log::ERR, \"socketpair failed (2)\");\n        die();\n        return false;\n    }\n\n    m_xwmFDs[0] = CFileDescriptor{xwmPair[0]};\n    m_xwmFDs[1] = CFileDescriptor{xwmPair[1]};\n\n    if (!m_xwmFDs[0].setFlags(m_xwmFDs[0].getFlags() | FD_CLOEXEC) || !m_xwmFDs[1].setFlags(m_xwmFDs[1].getFlags() | FD_CLOEXEC)) {\n        Log::logger->log(Log::ERR, \"set_cloexec failed (2)\");\n        die();\n        return false;\n    }\n\n    m_xwaylandClient = wl_client_create(g_pCompositor->m_wlDisplay, m_waylandFDs[0].get());\n    if (!m_xwaylandClient) {\n        Log::logger->log(Log::ERR, \"wl_client_create failed\");\n        die();\n        return false;\n    }\n\n    m_waylandFDs[0].take(); // wl_client owns this fd now\n\n    int notify[2] = {-1, -1};\n    if (pipe(notify) < 0) {\n        Log::logger->log(Log::ERR, \"pipe failed\");\n        die();\n        return false;\n    }\n\n    CFileDescriptor notifyFds[2] = {CFileDescriptor{notify[0]}, CFileDescriptor{notify[1]}};\n\n    if (!notifyFds[0].setFlags(notifyFds[0].getFlags() | FD_CLOEXEC)) {\n        Log::logger->log(Log::ERR, \"set_cloexec failed (3)\");\n        die();\n        return false;\n    }\n\n    m_pipeSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, notifyFds[0].get(), WL_EVENT_READABLE, ::xwaylandReady, nullptr);\n    m_pipeFd     = std::move(notifyFds[0]);\n\n    auto serverPID = fork();\n    if (serverPID < 0) {\n        Log::logger->log(Log::ERR, \"fork failed\");\n        die();\n        return false;\n    } else if (serverPID == 0) {\n        runXWayland(notifyFds[1]);\n        _exit(0);\n    }\n\n    return true;\n}\n\nint CXWaylandServer::ready(int fd, uint32_t mask) {\n    if (mask & WL_EVENT_READABLE) {\n        // xwayland writes twice\n        char    buf[64];\n        ssize_t n = read(fd, buf, sizeof(buf));\n        if (n < 0 && errno != EINTR) {\n            Log::logger->log(Log::ERR, \"Xwayland: read from displayFd failed\");\n            mask = 0;\n        } else if (n <= 0 || buf[n - 1] != '\\n')\n            return 1;\n    }\n\n    // if we don't have readable here, it failed\n    if (!(mask & WL_EVENT_READABLE)) {\n        Log::logger->log(Log::ERR, \"Xwayland: startup failed, not setting up xwm\");\n        g_pXWayland->m_server.reset();\n        return 1;\n    }\n\n    Log::logger->log(Log::DEBUG, \"XWayland is ready\");\n\n    wl_event_source_remove(m_pipeSource);\n    m_pipeFd.reset();\n    m_pipeSource = nullptr;\n\n    // start the wm\n    if (!g_pXWayland->m_wm)\n        g_pXWayland->m_wm = makeUnique<CXWM>();\n\n    g_pCursorManager->setXWaylandCursor();\n\n    return 0;\n}\n\n#endif\n"
  },
  {
    "path": "src/xwayland/Server.hpp",
    "content": "#pragma once\n\n#include <array>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include \"../helpers/signal/Signal.hpp\"\n\nstruct wl_event_source;\nstruct wl_client;\n\n// TODO: add lazy mode\nclass CXWaylandServer {\n  public:\n    CXWaylandServer();\n    ~CXWaylandServer();\n\n    // create the server.\n    bool create();\n\n    // starts the server, meant to be called by CXWaylandServer.\n    bool start();\n\n    // called on ready\n    int        ready(int fd, uint32_t mask);\n\n    void       die();\n\n    wl_client* m_xwaylandClient = nullptr;\n\n  private:\n    bool                                          tryOpenSockets();\n    void                                          runXWayland(Hyprutils::OS::CFileDescriptor& notifyFD);\n\n    std::string                                   m_displayName;\n    int                                           m_display = -1;\n    std::array<Hyprutils::OS::CFileDescriptor, 2> m_xFDs;\n    std::array<wl_event_source*, 2>               m_xFDReadEvents = {nullptr, nullptr};\n    wl_event_source*                              m_idleSource    = nullptr;\n    wl_event_source*                              m_pipeSource    = nullptr;\n    Hyprutils::OS::CFileDescriptor                m_pipeFd;\n    std::array<Hyprutils::OS::CFileDescriptor, 2> m_xwmFDs;\n    std::array<Hyprutils::OS::CFileDescriptor, 2> m_waylandFDs;\n\n    friend class CXWM;\n};\n"
  },
  {
    "path": "src/xwayland/XDataSource.cpp",
    "content": "#ifndef NO_XWAYLAND\n\n#include \"XWayland.hpp\"\n#include \"../defines.hpp\"\n#include \"XDataSource.hpp\"\n\n#include <fcntl.h>\nusing namespace Hyprutils::OS;\n\nCXDataSource::CXDataSource(SXSelection& sel_) : m_selection(sel_) {\n    xcb_get_property_cookie_t cookie = xcb_get_property(g_pXWayland->m_wm->getConnection(),\n                                                        1, // delete\n                                                        m_selection.window, HYPRATOMS[\"_WL_SELECTION\"], XCB_GET_PROPERTY_TYPE_ANY, 0, 4096);\n\n    xcb_get_property_reply_t* reply = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), cookie, nullptr);\n    if (!reply)\n        return;\n\n    if (reply->type != XCB_ATOM_ATOM) {\n        free(reply); // NOLINT(cppcoreguidelines-no-malloc)\n        return;\n    }\n\n    auto value = sc<xcb_atom_t*>(xcb_get_property_value(reply));\n    for (uint32_t i = 0; i < reply->value_len; i++) {\n        if (value[i] == HYPRATOMS[\"UTF8_STRING\"])\n            m_mimeTypes.emplace_back(\"text/plain;charset=utf-8\");\n        else if (value[i] == HYPRATOMS[\"TEXT\"])\n            m_mimeTypes.emplace_back(\"text/plain\");\n        else if (value[i] != HYPRATOMS[\"TARGETS\"] && value[i] != HYPRATOMS[\"TIMESTAMP\"]) {\n\n            auto type = g_pXWayland->m_wm->mimeFromAtom(value[i]);\n\n            if (type == \"INVALID\")\n                continue;\n\n            m_mimeTypes.push_back(type);\n        } else\n            continue;\n\n        m_mimeAtoms.push_back(value[i]);\n    }\n\n    free(reply); // NOLINT(cppcoreguidelines-no-malloc)\n}\n\nstd::vector<std::string> CXDataSource::mimes() {\n    return m_mimeTypes;\n}\n\nvoid CXDataSource::send(const std::string& mime, CFileDescriptor fd) {\n    xcb_atom_t mimeAtom = 0;\n\n    if (mime == \"text/plain\")\n        mimeAtom = HYPRATOMS[\"TEXT\"];\n    else if (mime == \"text/plain;charset=utf-8\")\n        mimeAtom = HYPRATOMS[\"UTF8_STRING\"];\n    else {\n        for (size_t i = 0; i < m_mimeTypes.size(); ++i) {\n            if (m_mimeTypes[i] == mime) {\n                mimeAtom = m_mimeAtoms[i];\n                break;\n            }\n        }\n    }\n\n    if (!mimeAtom) {\n        Log::logger->log(Log::ERR, \"[XDataSource] mime atom not found\");\n        return;\n    }\n\n    Log::logger->log(Log::DEBUG, \"[XDataSource] send with mime {} to fd {}\", mime, fd.get());\n\n    auto transfer            = makeUnique<SXTransfer>(m_selection);\n    transfer->incomingWindow = xcb_generate_id(g_pXWayland->m_wm->getConnection());\n    const uint32_t MASK      = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;\n    xcb_create_window(g_pXWayland->m_wm->getConnection(), XCB_COPY_FROM_PARENT, transfer->incomingWindow, g_pXWayland->m_wm->m_screen->root, 0, 0, 10, 10, 0,\n                      XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pXWayland->m_wm->m_screen->root_visual, XCB_CW_EVENT_MASK, &MASK);\n\n    xcb_atom_t selection_atom = HYPRATOMS[\"CLIPBOARD\"];\n    if (&m_selection == &g_pXWayland->m_wm->m_primarySelection)\n        selection_atom = HYPRATOMS[\"PRIMARY\"];\n    else if (&m_selection == &g_pXWayland->m_wm->m_dndSelection)\n        selection_atom = HYPRATOMS[\"XdndSelection\"];\n\n    xcb_convert_selection(g_pXWayland->m_wm->getConnection(), transfer->incomingWindow, selection_atom, mimeAtom, HYPRATOMS[\"_WL_SELECTION\"], XCB_TIME_CURRENT_TIME);\n\n    xcb_flush(g_pXWayland->m_wm->getConnection());\n\n    //TODO: make CFileDescriptor setflags take SETFL as well\n    fcntl(fd.get(), F_SETFL, O_WRONLY | O_NONBLOCK);\n    transfer->wlFD = std::move(fd);\n    m_selection.transfers.emplace_back(std::move(transfer));\n}\n\nvoid CXDataSource::accepted(const std::string& mime) {\n    Log::logger->log(Log::DEBUG, \"[XDataSource] accepted is a stub\");\n}\n\nvoid CXDataSource::cancelled() {\n    Log::logger->log(Log::DEBUG, \"[XDataSource] cancelled is a stub\");\n}\n\nvoid CXDataSource::error(uint32_t code, const std::string& msg) {\n    Log::logger->log(Log::DEBUG, \"[XDataSource] error is a stub: err {}: {}\", code, msg);\n}\n\neDataSourceType CXDataSource::type() {\n    return DATA_SOURCE_TYPE_X11;\n}\n\n#endif\n"
  },
  {
    "path": "src/xwayland/XDataSource.hpp",
    "content": "#pragma once\n\n#include \"../protocols/types/DataDevice.hpp\"\n#include <hyprutils/os/FileDescriptor.hpp>\n\nstruct SXSelection;\n\nclass CXDataSource : public IDataSource {\n  public:\n    CXDataSource(SXSelection&);\n\n    virtual std::vector<std::string> mimes();\n    virtual void                     send(const std::string& mime, Hyprutils::OS::CFileDescriptor fd);\n    virtual void                     accepted(const std::string& mime);\n    virtual void                     cancelled();\n    virtual void                     error(uint32_t code, const std::string& msg);\n    virtual eDataSourceType          type();\n\n  private:\n    SXSelection&             m_selection;\n    std::vector<std::string> m_mimeTypes; // these two have shared idx\n    std::vector<uint32_t>    m_mimeAtoms; //\n};\n"
  },
  {
    "path": "src/xwayland/XSurface.cpp",
    "content": "#include \"XSurface.hpp\"\n#include \"XWayland.hpp\"\n#include \"../protocols/XWaylandShell.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../managers/ANRManager.hpp\"\n#include \"../helpers/time/Time.hpp\"\n\n#ifndef NO_XWAYLAND\n\nCXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_xID(xID_), m_geometry(geometry_), m_overrideRedirect(OR) {\n    xcb_res_query_client_ids_cookie_t client_id_cookie = {0};\n    if (g_pXWayland->m_wm->m_xres) {\n        xcb_res_client_id_spec_t spec = {.client = m_xID, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID};\n        client_id_cookie              = xcb_res_query_client_ids(g_pXWayland->m_wm->getConnection(), 1, &spec);\n    }\n\n    uint32_t values[1];\n    values[0] = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE;\n    xcb_change_window_attributes(g_pXWayland->m_wm->getConnection(), m_xID, XCB_CW_EVENT_MASK, values);\n\n    if (g_pXWayland->m_wm->m_xres) {\n        xcb_res_query_client_ids_reply_t* reply = xcb_res_query_client_ids_reply(g_pXWayland->m_wm->getConnection(), client_id_cookie, nullptr);\n        if (!reply)\n            return;\n\n        uint32_t*                          ppid = nullptr;\n        xcb_res_client_id_value_iterator_t iter = xcb_res_query_client_ids_ids_iterator(reply);\n        while (iter.rem > 0) {\n            if (iter.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID && xcb_res_client_id_value_value_length(iter.data) > 0) {\n                ppid = xcb_res_client_id_value_value(iter.data);\n                break;\n            }\n            xcb_res_client_id_value_next(&iter);\n        }\n        if (!ppid) {\n            free(reply); // NOLINT(cppcoreguidelines-no-malloc)\n            return;\n        }\n        m_pid = *ppid;\n        free(reply); // NOLINT(cppcoreguidelines-no-malloc)\n    }\n\n    // FIXME: this is a race, we need to listen to props changed\n    recheckSupportedProps();\n\n    m_events.resourceChange.listenStatic([this] { ensureListeners(); });\n}\n\nvoid CXWaylandSurface::recheckSupportedProps() {\n    m_supportedProps.clear();\n\n    auto  listCookie = xcb_list_properties(g_pXWayland->m_wm->getConnection(), m_xID);\n    auto* listReply  = xcb_list_properties_reply(g_pXWayland->m_wm->getConnection(), listCookie, nullptr);\n    auto  getCookie  = xcb_get_property(g_pXWayland->m_wm->getConnection(), 0, m_xID, HYPRATOMS[\"WM_PROTOCOLS\"], XCB_ATOM_ATOM, 0, 32);\n    auto* getReply   = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), getCookie, nullptr);\n\n    if (listReply) {\n        const auto* atoms = xcb_list_properties_atoms(listReply);\n        auto        len   = xcb_list_properties_atoms_length(listReply);\n\n        for (auto i = 0; i < len; ++i) {\n            m_supportedProps[atoms[i]] = true;\n        }\n\n        free(listReply);\n    }\n\n    if (getReply) {\n        const auto* protocols = sc<xcb_atom_t*>(xcb_get_property_value(getReply));\n        const auto  len       = xcb_get_property_value_length(getReply) / sizeof(xcb_atom_t);\n\n        for (auto i = 0u; i < len; ++i) {\n            m_supportedProps[protocols[i]] = true;\n        }\n\n        free(getReply);\n    }\n}\n\nvoid CXWaylandSurface::ensureListeners() {\n    bool connected = m_listeners.destroySurface;\n\n    if (connected && !m_surface) {\n        m_listeners.destroySurface.reset();\n        m_listeners.commitSurface.reset();\n    } else if (!connected && m_surface) {\n        m_listeners.destroySurface = m_surface->m_events.destroy.listen([this] {\n            if (m_mapped)\n                unmap();\n\n            m_surface.reset();\n            m_listeners.destroySurface.reset();\n            m_listeners.commitSurface.reset();\n            m_events.resourceChange.emit();\n        });\n\n        m_listeners.commitSurface = m_surface->m_events.commit.listen([this] {\n            if (m_surface->m_current.texture && !m_mapped) {\n                map();\n                return;\n            }\n\n            if (!m_surface->m_current.texture && m_mapped) {\n                unmap();\n                return;\n            }\n\n            m_events.commit.emit();\n        });\n    }\n\n    if (m_resource) {\n        m_listeners.destroyResource = m_resource->events.destroy.listen([this] {\n            unmap();\n            m_surface.reset();\n            m_events.resourceChange.emit();\n        });\n    }\n}\n\nvoid CXWaylandSurface::map() {\n    if (m_mapped)\n        return;\n\n    ASSERT(m_surface);\n\n    g_pXWayland->m_wm->m_mappedSurfaces.emplace_back(m_self);\n    g_pXWayland->m_wm->m_mappedSurfacesStacking.emplace_back(m_self);\n\n    m_mapped = true;\n    m_surface->map();\n\n    Log::logger->log(Log::DEBUG, \"XWayland surface {:x} mapping\", rc<uintptr_t>(this));\n\n    m_events.map.emit();\n\n    g_pXWayland->m_wm->updateClientList();\n}\n\nvoid CXWaylandSurface::unmap() {\n    if (!m_mapped)\n        return;\n\n    ASSERT(m_surface);\n\n    std::erase(g_pXWayland->m_wm->m_mappedSurfaces, m_self);\n    std::erase(g_pXWayland->m_wm->m_mappedSurfacesStacking, m_self);\n\n    m_mapped = false;\n    m_events.unmap.emit();\n    m_surface->unmap();\n\n    Log::logger->log(Log::DEBUG, \"XWayland surface {:x} unmapping\", rc<uintptr_t>(this));\n\n    g_pXWayland->m_wm->updateClientList();\n}\n\nvoid CXWaylandSurface::considerMap() {\n    if (m_mapped)\n        return;\n\n    if (!m_surface) {\n        Log::logger->log(Log::DEBUG, \"XWayland surface: considerMap, nope, no surface\");\n        return;\n    }\n\n    if (m_surface->m_current.texture) {\n        Log::logger->log(Log::DEBUG, \"XWayland surface: considerMap, sure, we have a buffer\");\n        map();\n        return;\n    }\n\n    Log::logger->log(Log::DEBUG, \"XWayland surface: considerMap, nope, we don't have a buffer\");\n}\n\nbool CXWaylandSurface::wantsFocus() {\n    if (m_atoms.empty())\n        return true;\n\n    const std::array<uint32_t, 10> search = {\n        HYPRATOMS[\"_NET_WM_WINDOW_TYPE_COMBO\"],   HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DND\"],          HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\"],\n        HYPRATOMS[\"_NET_WM_WINDOW_TYPE_MENU\"],    HYPRATOMS[\"_NET_WM_WINDOW_TYPE_NOTIFICATION\"], HYPRATOMS[\"_NET_WM_WINDOW_TYPE_POPUP_MENU\"],\n        HYPRATOMS[\"_NET_WM_WINDOW_TYPE_SPLASH\"],  HYPRATOMS[\"_NET_WM_WINDOW_TYPE_DESKTOP\"],      HYPRATOMS[\"_NET_WM_WINDOW_TYPE_TOOLTIP\"],\n        HYPRATOMS[\"_NET_WM_WINDOW_TYPE_UTILITY\"],\n    };\n\n    for (auto const& searched : search) {\n        for (auto const& a : m_atoms) {\n            if (a == searched)\n                return false;\n        }\n    }\n\n    return true;\n}\n\nvoid CXWaylandSurface::configure(const CBox& box) {\n    Vector2D oldSize = m_geometry.size();\n\n    m_geometry = box;\n\n    uint32_t mask     = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH;\n    uint32_t values[] = {box.x, box.y, box.width, box.height, 0};\n    xcb_configure_window(g_pXWayland->m_wm->getConnection(), m_xID, mask, values);\n\n    if (m_geometry.width == box.width && m_geometry.height == box.height) {\n        // ICCCM requires a synthetic event when window size is not changed\n        xcb_configure_notify_event_t e;\n        e.response_type     = XCB_CONFIGURE_NOTIFY;\n        e.event             = m_xID;\n        e.window            = m_xID;\n        e.x                 = box.x;\n        e.y                 = box.y;\n        e.width             = box.width;\n        e.height            = box.height;\n        e.border_width      = 0;\n        e.above_sibling     = XCB_NONE;\n        e.override_redirect = m_overrideRedirect;\n        xcb_send_event(g_pXWayland->m_wm->getConnection(), false, m_xID, XCB_EVENT_MASK_STRUCTURE_NOTIFY, rc<const char*>(&e));\n    }\n\n    g_pXWayland->m_wm->updateClientList();\n\n    xcb_flush(g_pXWayland->m_wm->getConnection());\n}\n\nvoid CXWaylandSurface::activate(bool activate) {\n    if (m_overrideRedirect && !activate)\n        return;\n    setWithdrawn(false);\n    g_pXWayland->m_wm->activateSurface(m_self.lock(), activate);\n}\n\nvoid CXWaylandSurface::setFullscreen(bool fs) {\n    m_fullscreen = fs;\n    g_pXWayland->m_wm->sendState(m_self.lock());\n}\n\nvoid CXWaylandSurface::setMinimized(bool mz) {\n    m_minimized = mz;\n    g_pXWayland->m_wm->sendState(m_self.lock());\n}\n\nvoid CXWaylandSurface::restackToTop() {\n    uint32_t values[1] = {XCB_STACK_MODE_ABOVE};\n\n    xcb_configure_window(g_pXWayland->m_wm->getConnection(), m_xID, XCB_CONFIG_WINDOW_STACK_MODE, values);\n\n    auto& stack = g_pXWayland->m_wm->m_mappedSurfacesStacking;\n    auto  it    = std::ranges::find(stack, m_self);\n\n    if (it != stack.end())\n        std::rotate(it, it + 1, stack.end());\n\n    g_pXWayland->m_wm->updateClientList();\n\n    xcb_flush(g_pXWayland->m_wm->getConnection());\n}\n\nvoid CXWaylandSurface::close() {\n\n    // Recheck the supported props, check if we maybe have WM_DELETE_WINDOW.\n    recheckSupportedProps();\n\n    if (m_supportedProps[HYPRATOMS[\"WM_DELETE_WINDOW\"]]) {\n        xcb_client_message_data_t msg = {};\n        msg.data32[0]                 = HYPRATOMS[\"WM_DELETE_WINDOW\"];\n        msg.data32[1]                 = XCB_CURRENT_TIME;\n        g_pXWayland->m_wm->sendWMMessage(m_self.lock(), &msg, XCB_EVENT_MASK_NO_EVENT);\n    } else {\n        xcb_kill_client(g_pXWayland->m_wm->getConnection(), m_self->m_xID);\n        xcb_flush(g_pXWayland->m_wm->getConnection());\n    }\n}\n\nvoid CXWaylandSurface::setWithdrawn(bool withdrawn_) {\n    m_withdrawn                 = withdrawn_;\n    std::vector<uint32_t> props = {XCB_ICCCM_WM_STATE_NORMAL, XCB_WINDOW_NONE};\n\n    if (m_withdrawn)\n        props[0] = XCB_ICCCM_WM_STATE_WITHDRAWN;\n    else if (m_minimized)\n        props[0] = XCB_ICCCM_WM_STATE_ICONIC;\n    else\n        props[0] = XCB_ICCCM_WM_STATE_NORMAL;\n\n    xcb_change_property(g_pXWayland->m_wm->getConnection(), XCB_PROP_MODE_REPLACE, m_xID, HYPRATOMS[\"WM_STATE\"], HYPRATOMS[\"WM_STATE\"], 32, props.size(), props.data());\n}\n\nvoid CXWaylandSurface::ping() {\n    bool supportsPing = std::ranges::find(m_protocols, HYPRATOMS[\"_NET_WM_PING\"]) != m_protocols.end();\n\n    if (!supportsPing) {\n        Log::logger->log(Log::TRACE, \"CXWaylandSurface: XID {} does not support ping, just sending an instant reply\", m_xID);\n        g_pANRManager->onResponse(m_self.lock());\n        return;\n    }\n\n    xcb_client_message_data_t msg = {};\n    msg.data32[0]                 = HYPRATOMS[\"_NET_WM_PING\"];\n    msg.data32[1]                 = Time::millis(Time::steadyNow());\n    msg.data32[2]                 = m_xID;\n\n    m_lastPingSeq = msg.data32[1];\n\n    g_pXWayland->m_wm->sendWMMessage(m_self.lock(), &msg, XCB_EVENT_MASK_PROPERTY_CHANGE);\n}\n\n#else\n\nCXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_xID(xID_), m_geometry(geometry_), m_overrideRedirect(OR) {\n    ;\n}\n\nvoid CXWaylandSurface::ensureListeners() {\n    ;\n}\n\nvoid CXWaylandSurface::map() {\n    ;\n}\n\nvoid CXWaylandSurface::unmap() {\n    ;\n}\n\nbool CXWaylandSurface::wantsFocus() {\n    return false;\n}\n\nvoid CXWaylandSurface::configure(const CBox& box) {\n    ;\n}\n\nvoid CXWaylandSurface::activate(bool activate) {\n    ;\n}\n\nvoid CXWaylandSurface::setFullscreen(bool fs) {\n    ;\n}\n\nvoid CXWaylandSurface::setMinimized(bool mz) {\n    ;\n}\n\nvoid CXWaylandSurface::restackToTop() {\n    ;\n}\n\nvoid CXWaylandSurface::close() {\n    ;\n}\n\nvoid CXWaylandSurface::considerMap() {\n    ;\n}\n\nvoid CXWaylandSurface::setWithdrawn(bool withdrawn) {\n    ;\n}\n\nvoid CXWaylandSurface::ping() {\n    ;\n}\n\n#endif\n"
  },
  {
    "path": "src/xwayland/XSurface.hpp",
    "content": "#pragma once\n\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../helpers/math/Math.hpp\"\n#include <vector>\n\nclass CWLSurfaceResource;\nclass CXWaylandSurfaceResource;\n\n#ifdef NO_XWAYLAND\nusing xcb_pixmap_t         = uint32_t;\nusing xcb_window_t         = uint32_t;\nusing xcb_atom_t           = uint32_t;\nusing xcb_icccm_wm_hints_t = struct {\n    int32_t      flags;\n    uint32_t     input;\n    int32_t      initial_state;\n    xcb_pixmap_t icon_pixmap;\n    xcb_window_t icon_window;\n    int32_t      icon_x, icon_y;\n    xcb_pixmap_t icon_mask;\n    xcb_window_t window_group;\n};\nusing xcb_size_hints_t = struct {\n    uint32_t flags;\n    int32_t  x, y;\n    int32_t  width, height;\n    int32_t  min_width, min_height;\n    int32_t  max_width, max_height;\n    int32_t  width_inc, height_inc;\n    int32_t  min_aspect_num, min_aspect_den;\n    int32_t  max_aspect_num, max_aspect_den;\n    int32_t  base_width, base_height;\n    uint32_t win_gravity;\n};\n#else\n#include <xcb/xcb_icccm.h>\n#endif\n\nclass CXWaylandSurface {\n  public:\n    WP<CWLSurfaceResource>       m_surface;\n    WP<CXWaylandSurfaceResource> m_resource;\n\n    struct {\n        CSignalT<>     stateChanged;    // maximized, fs, minimized, etc.\n        CSignalT<>     metadataChanged; // title, appid\n        CSignalT<>     destroy;\n\n        CSignalT<>     resourceChange; // associated / dissociated\n\n        CSignalT<>     setGeometry;\n        CSignalT<CBox> configureRequest;\n\n        CSignalT<>     map;\n        CSignalT<>     unmap;\n        CSignalT<>     commit;\n\n        CSignalT<>     activate;\n    } m_events;\n\n    struct {\n        std::string title;\n        std::string appid;\n\n        // volatile state: is reset after the stateChanged signal fires\n        std::optional<bool> requestsMaximize;\n        std::optional<bool> requestsFullscreen;\n        std::optional<bool> requestsMinimize;\n    } m_state;\n\n    uint32_t                          m_xID         = 0;\n    uint64_t                          m_wlID        = 0;\n    uint64_t                          m_wlSerial    = 0;\n    uint32_t                          m_lastPingSeq = 0;\n    pid_t                             m_pid         = 0;\n    CBox                              m_geometry;\n    bool                              m_overrideRedirect = false;\n    bool                              m_withdrawn        = false;\n    bool                              m_fullscreen       = false;\n    bool                              m_maximized        = false;\n    bool                              m_minimized        = false;\n    bool                              m_mapped           = false;\n    bool                              m_modal            = false;\n\n    WP<CXWaylandSurface>              m_parent;\n    WP<CXWaylandSurface>              m_self;\n    std::vector<WP<CXWaylandSurface>> m_children;\n\n    UP<xcb_icccm_wm_hints_t>          m_hints;\n    UP<xcb_size_hints_t>              m_sizeHints;\n    std::vector<uint32_t>             m_atoms;\n    std::vector<uint32_t>             m_protocols;\n    std::string                       m_role      = \"\";\n    bool                              m_transient = false;\n\n    bool                              wantsFocus();\n    void                              configure(const CBox& box);\n    void                              activate(bool activate);\n    void                              setFullscreen(bool fs);\n    void                              setMinimized(bool mz);\n    void                              restackToTop();\n    void                              close();\n    void                              ping();\n\n  private:\n    CXWaylandSurface(uint32_t xID, CBox geometry, bool OR);\n\n    void ensureListeners();\n    void map();\n    void unmap();\n    void considerMap();\n    void setWithdrawn(bool withdrawn);\n    void recheckSupportedProps();\n\n    struct {\n        CHyprSignalListener destroyResource;\n        CHyprSignalListener destroySurface;\n        CHyprSignalListener commitSurface;\n    } m_listeners;\n\n    std::unordered_map<xcb_atom_t, bool> m_supportedProps;\n\n    friend class CXWM;\n};\n"
  },
  {
    "path": "src/xwayland/XWM.cpp",
    "content": "#include <cstddef>\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <xcb/xfixes.h>\n#include <xcb/xproto.h>\n#ifndef NO_XWAYLAND\n\n#include <fcntl.h>\n#include <cstring>\n#include <algorithm>\n#include <unordered_map>\n#include <xcb/xcb_icccm.h>\n\n#include \"XWayland.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../protocols/core/Seat.hpp\"\n#include \"../managers/eventLoop/EventLoopManager.hpp\"\n#include \"../managers/SeatManager.hpp\"\n#include \"../managers/ANRManager.hpp\"\n#include \"../helpers/env/Env.hpp\"\n#include \"../protocols/XWaylandShell.hpp\"\n#include \"../protocols/core/Compositor.hpp\"\n#include \"../desktop/state/FocusState.hpp\"\nusing Hyprutils::Memory::CUniquePointer;\n\nusing namespace Hyprutils::OS;\n\n#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f\nconstexpr size_t INCR_CHUNK_SIZE = 64ul * 1024;\n\nstatic int       onX11Event(int fd, uint32_t mask, void* data) {\n    return g_pXWayland->m_wm->onEvent(fd, mask);\n}\n\nstatic int writeDataSource(int fd, uint32_t mask, void* data);\n\nstruct SFreeDeleter {\n    void operator()(void* ptr) const {\n        std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc)\n    }\n};\n\ntemplate <typename T>\nusing XCBReplyPtr = std::unique_ptr<T, SFreeDeleter>;\n\nSP<CXWaylandSurface> CXWM::windowForXID(xcb_window_t wid) {\n    for (auto const& s : m_surfaces) {\n        if (s->m_xID == wid)\n            return s;\n    }\n\n    return nullptr;\n}\n\nvoid CXWM::handleCreate(xcb_create_notify_event_t* e) {\n    if (isWMWindow(e->window))\n        return;\n\n    const auto XSURF = m_surfaces.emplace_back(SP<CXWaylandSurface>(new CXWaylandSurface(e->window, CBox{e->x, e->y, e->width, e->height}, e->override_redirect)));\n    XSURF->m_self    = XSURF;\n    Log::logger->log(Log::DEBUG, \"[xwm] New XSurface at {:x} with xid of {}\", rc<uintptr_t>(XSURF.get()), e->window);\n\n    const auto WINDOW = Desktop::View::CWindow::create(XSURF);\n    g_pCompositor->m_windows.emplace_back(WINDOW);\n    WINDOW->m_self = WINDOW;\n    Log::logger->log(Log::DEBUG, \"[xwm] New XWayland window at {:x} for surf {:x}\", rc<uintptr_t>(WINDOW.get()), rc<uintptr_t>(XSURF.get()));\n}\n\nvoid CXWM::handleDestroy(xcb_destroy_notify_event_t* e) {\n    removeTransfersForWindow(e->window);\n\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    XSURF->m_events.destroy.emit();\n    std::erase_if(m_surfaces, [XSURF](const auto& other) { return XSURF == other; });\n}\n\nvoid CXWM::handleConfigureRequest(xcb_configure_request_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    const uint16_t     MASK     = e->value_mask;\n    constexpr uint16_t GEOMETRY = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;\n    if (!(MASK & GEOMETRY))\n        return;\n\n    XSURF->m_events.configureRequest.emit(CBox{MASK & XCB_CONFIG_WINDOW_X ? e->x : XSURF->m_geometry.x, MASK & XCB_CONFIG_WINDOW_Y ? e->y : XSURF->m_geometry.y,\n                                               MASK & XCB_CONFIG_WINDOW_WIDTH ? e->width : XSURF->m_geometry.width,\n                                               MASK & XCB_CONFIG_WINDOW_HEIGHT ? e->height : XSURF->m_geometry.height});\n}\n\nvoid CXWM::handleConfigureNotify(xcb_configure_notify_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    if (XSURF->m_geometry == CBox{e->x, e->y, e->width, e->height})\n        return;\n\n    XSURF->m_geometry = {e->x, e->y, e->width, e->height};\n    updateOverrideRedirect(XSURF, e->override_redirect);\n    XSURF->m_events.setGeometry.emit();\n}\n\nvoid CXWM::handleMapRequest(xcb_map_request_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    xcb_map_window(getConnection(), e->window);\n\n    XSURF->restackToTop();\n\n    const bool SMALL =\n        XSURF->m_geometry.size() < Vector2D{2, 2} || (XSURF->m_sizeHints && XSURF->m_geometry.size() < Vector2D{XSURF->m_sizeHints->min_width, XSURF->m_sizeHints->min_height});\n    const bool HAS_HINTS   = XSURF->m_sizeHints && Vector2D{XSURF->m_sizeHints->base_width, XSURF->m_sizeHints->base_height} > Vector2D{5, 5};\n    const auto DESIREDSIZE = HAS_HINTS ? Vector2D{XSURF->m_sizeHints->base_width, XSURF->m_sizeHints->base_height} : Vector2D{800, 800};\n\n    // if it's too small, configure it.\n    if (SMALL && !XSURF->m_overrideRedirect) // default to 800 x 800\n        XSURF->configure({XSURF->m_geometry.pos(), DESIREDSIZE});\n\n    Log::logger->log(Log::DEBUG, \"[xwm] Mapping window {} in X (geometry {}x{} at {}x{}))\", e->window, XSURF->m_geometry.width, XSURF->m_geometry.height, XSURF->m_geometry.x,\n                     XSURF->m_geometry.y);\n\n    // read data again. Some apps for some reason fail to send WINDOW_TYPE\n    // this shouldn't happen but does, I prolly fucked up somewhere, this is a band-aid\n    readWindowData(XSURF);\n\n    XSURF->considerMap();\n}\n\nvoid CXWM::handleMapNotify(xcb_map_notify_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    updateOverrideRedirect(XSURF, e->override_redirect);\n\n    if (XSURF->m_overrideRedirect)\n        return;\n\n    XSURF->setWithdrawn(false);\n    sendState(XSURF);\n    xcb_flush(getConnection());\n\n    XSURF->considerMap();\n}\n\nvoid CXWM::handleUnmapNotify(xcb_unmap_notify_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    XSURF->unmap();\n    dissociate(XSURF);\n\n    if (XSURF->m_overrideRedirect)\n        return;\n\n    XSURF->setWithdrawn(true);\n    sendState(XSURF);\n    xcb_flush(getConnection());\n}\n\nstatic bool lookupParentExists(SP<CXWaylandSurface> XSURF, SP<CXWaylandSurface> prospectiveChild) {\n    std::vector<SP<CXWaylandSurface>> visited;\n\n    while (XSURF->m_parent) {\n        if (XSURF->m_parent == prospectiveChild)\n            return true;\n        visited.emplace_back(XSURF);\n\n        XSURF = XSURF->m_parent.lock();\n\n        if (std::ranges::find(visited, XSURF) != visited.end())\n            return false;\n    }\n\n    return false;\n}\n\nstd::string CXWM::getAtomName(uint32_t atom) {\n    for (auto const& ha : HYPRATOMS) {\n        if (ha.second != atom)\n            continue;\n\n        return ha.first;\n    }\n\n    // Get the name of the atom\n    const auto                             cookie = xcb_get_atom_name(getConnection(), atom);\n    XCBReplyPtr<xcb_get_atom_name_reply_t> reply(xcb_get_atom_name_reply(getConnection(), cookie, nullptr));\n\n    if (!reply)\n        return \"Unknown\";\n\n    auto const name_len = xcb_get_atom_name_name_length(reply.get());\n    auto*      name     = xcb_get_atom_name_name(reply.get());\n\n    return {name, name_len};\n}\n\nvoid CXWM::readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply) {\n    std::string propName;\n    if (Env::isTrace())\n        propName = getAtomName(atom);\n\n    const auto  valueLen = xcb_get_property_value_length(reply);\n    const auto* value    = sc<const char*>(xcb_get_property_value(reply));\n\n    auto        handleWMClass = [&]() {\n        XSURF->m_state.appid = std::string{value, valueLen};\n        if (std::count(XSURF->m_state.appid.begin(), XSURF->m_state.appid.end(), '\\000') == 2)\n            XSURF->m_state.appid = XSURF->m_state.appid.substr(XSURF->m_state.appid.find_first_of('\\000') + 1);\n\n        if (!XSURF->m_state.appid.empty())\n            XSURF->m_state.appid.pop_back();\n        XSURF->m_events.metadataChanged.emit();\n    };\n\n    auto handleWMName = [&]() {\n        if (reply->type != HYPRATOMS[\"UTF8_STRING\"] && reply->type != HYPRATOMS[\"TEXT\"] && reply->type != XCB_ATOM_STRING)\n            return;\n        XSURF->m_state.title = std::string{value, valueLen};\n        XSURF->m_events.metadataChanged.emit();\n    };\n\n    auto handleWindowType = [&]() {\n        auto* atomsArr = rc<const xcb_atom_t*>(value);\n        XSURF->m_atoms.assign(atomsArr, atomsArr + reply->value_len);\n    };\n\n    auto handleWMState = [&]() {\n        auto* atoms = rc<const xcb_atom_t*>(value);\n        for (uint32_t i = 0; i < reply->value_len; i++) {\n            if (atoms[i] == HYPRATOMS[\"_NET_WM_STATE_MODAL\"])\n                XSURF->m_modal = true;\n        }\n    };\n\n    auto handleWMHints = [&]() {\n        if (reply->value_len == 0)\n            return;\n        XSURF->m_hints = makeUnique<xcb_icccm_wm_hints_t>();\n        xcb_icccm_get_wm_hints_from_reply(XSURF->m_hints.get(), reply);\n        if (!(XSURF->m_hints->flags & XCB_ICCCM_WM_HINT_INPUT))\n            XSURF->m_hints->input = true;\n    };\n\n    auto handleWMRole = [&]() {\n        if (valueLen <= 0)\n            XSURF->m_role = \"\";\n        else {\n            XSURF->m_role = std::string{value, valueLen};\n            XSURF->m_role = XSURF->m_role.substr(0, XSURF->m_role.find_first_of('\\000'));\n        }\n    };\n\n    auto handleTransientFor = [&]() {\n        if (reply->type != XCB_ATOM_WINDOW)\n            return;\n        const auto XID     = rc<const xcb_window_t*>(value);\n        XSURF->m_transient = XID;\n        if (!XID)\n            return;\n\n        if (const auto NEWXSURF = windowForXID(*XID); NEWXSURF && !lookupParentExists(XSURF, NEWXSURF)) {\n            XSURF->m_parent = NEWXSURF;\n            NEWXSURF->m_children.emplace_back(XSURF);\n        } else\n            Log::logger->log(Log::DEBUG, \"[xwm] Denying transient because it would create a loop\");\n    };\n\n    auto handleSizeHints = [&]() {\n        if (reply->type != HYPRATOMS[\"WM_SIZE_HINTS\"] || reply->value_len == 0)\n            return;\n\n        XSURF->m_sizeHints = makeUnique<xcb_size_hints_t>();\n        std::memset(XSURF->m_sizeHints.get(), 0, sizeof(xcb_size_hints_t));\n        xcb_icccm_get_wm_size_hints_from_reply(XSURF->m_sizeHints.get(), reply);\n\n        const int32_t FLAGS   = XSURF->m_sizeHints->flags;\n        const bool    HASMIN  = FLAGS & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE;\n        const bool    HASBASE = FLAGS & XCB_ICCCM_SIZE_HINT_BASE_SIZE;\n\n        if (!HASMIN && !HASBASE) {\n            XSURF->m_sizeHints->min_width = XSURF->m_sizeHints->min_height = -1;\n            XSURF->m_sizeHints->base_width = XSURF->m_sizeHints->base_height = -1;\n        } else if (!HASBASE) {\n            XSURF->m_sizeHints->base_width  = XSURF->m_sizeHints->min_width;\n            XSURF->m_sizeHints->base_height = XSURF->m_sizeHints->min_height;\n        } else if (!HASMIN) {\n            XSURF->m_sizeHints->min_width  = XSURF->m_sizeHints->base_width;\n            XSURF->m_sizeHints->min_height = XSURF->m_sizeHints->base_height;\n        }\n\n        if (!(FLAGS & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE))\n            XSURF->m_sizeHints->max_width = XSURF->m_sizeHints->max_height = -1;\n    };\n\n    auto handleWMProtocols = [&]() {\n        if (reply->type != XCB_ATOM_ATOM)\n            return;\n        auto* atoms = rc<const xcb_atom_t*>(value);\n        XSURF->m_protocols.assign(atoms, atoms + reply->value_len);\n    };\n\n    if (atom == XCB_ATOM_WM_CLASS)\n        handleWMClass();\n    else if (atom == XCB_ATOM_WM_NAME || atom == HYPRATOMS[\"_NET_WM_NAME\"])\n        handleWMName();\n    else if (atom == HYPRATOMS[\"_NET_WM_WINDOW_TYPE\"])\n        handleWindowType();\n    else if (atom == HYPRATOMS[\"_NET_WM_STATE\"])\n        handleWMState();\n    else if (atom == HYPRATOMS[\"WM_HINTS\"])\n        handleWMHints();\n    else if (atom == HYPRATOMS[\"WM_WINDOW_ROLE\"])\n        handleWMRole();\n    else if (atom == XCB_ATOM_WM_TRANSIENT_FOR)\n        handleTransientFor();\n    else if (atom == HYPRATOMS[\"WM_NORMAL_HINTS\"])\n        handleSizeHints();\n    else if (atom == HYPRATOMS[\"WM_PROTOCOLS\"])\n        handleWMProtocols();\n    else {\n        Log::logger->log(Log::TRACE, \"[xwm] Unhandled prop {} -> {}\", atom, propName);\n        return;\n    }\n\n    Log::logger->log(Log::TRACE, \"[xwm] Handled prop {} -> {}\", atom, propName);\n}\n\nvoid CXWM::handlePropertyNotify(xcb_property_notify_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF) {\n        removeTransfersForWindow(e->window);\n        return;\n    }\n\n    xcb_get_property_cookie_t             cookie = xcb_get_property(getConnection(), 0, XSURF->m_xID, e->atom, XCB_ATOM_ANY, 0, 2048);\n    XCBReplyPtr<xcb_get_property_reply_t> reply(xcb_get_property_reply(getConnection(), cookie, nullptr));\n\n    if (!reply) {\n        Log::logger->log(Log::ERR, \"[xwm] Failed to read property notify cookie for window {}\", e->window);\n        removeTransfersForWindow(e->window);\n        return;\n    }\n\n    readProp(XSURF, e->atom, reply.get());\n}\n\nvoid CXWM::handleClientMessage(xcb_client_message_event_t* e) {\n    const auto XSURF = windowForXID(e->window);\n\n    if (!XSURF)\n        return;\n\n    std::string propName = getAtomName(e->type);\n\n    if (e->type == HYPRATOMS[\"WM_PROTOCOLS\"]) {\n        if (e->data.data32[1] == XSURF->m_lastPingSeq && e->data.data32[0] == HYPRATOMS[\"_NET_WM_PING\"]) {\n            g_pANRManager->onResponse(XSURF);\n            return;\n        }\n    } else if (e->type == HYPRATOMS[\"WL_SURFACE_ID\"]) {\n        if (XSURF->m_surface) {\n            Log::logger->log(Log::WARN, \"[xwm] Re-assignment of WL_SURFACE_ID\");\n            dissociate(XSURF);\n        }\n\n        auto id       = e->data.data32[0];\n        auto resource = wl_client_get_object(g_pXWayland->m_server->m_xwaylandClient, id);\n        if (resource) {\n            auto surf = CWLSurfaceResource::fromResource(resource);\n            associate(XSURF, surf);\n        }\n    } else if (e->type == HYPRATOMS[\"WL_SURFACE_SERIAL\"]) {\n        if (XSURF->m_wlSerial) {\n            Log::logger->log(Log::WARN, \"[xwm] Re-assignment of WL_SURFACE_SERIAL\");\n            dissociate(XSURF);\n        }\n\n        uint32_t serialLow  = e->data.data32[0];\n        uint32_t serialHigh = e->data.data32[1];\n        XSURF->m_wlSerial   = (sc<uint64_t>(serialHigh) << 32) | serialLow;\n\n        Log::logger->log(Log::DEBUG, \"[xwm] surface {:x} requests serial {:x}\", rc<uintptr_t>(XSURF.get()), XSURF->m_wlSerial);\n\n        for (auto const& res : m_shellResources) {\n            if (!res)\n                continue;\n\n            if (res->m_serial != XSURF->m_wlSerial || !XSURF->m_wlSerial)\n                continue;\n\n            associate(XSURF, res->m_surface.lock());\n            break;\n        }\n\n    } else if (e->type == HYPRATOMS[\"_NET_WM_STATE\"]) {\n        if (e->format == 32) {\n            uint32_t action = e->data.data32[0];\n            for (size_t i = 0; i < 2; ++i) {\n                xcb_atom_t prop = e->data.data32[1 + i];\n\n                auto       updateState = [XSURF](int action, bool current) -> bool {\n                    switch (action) {\n                        case 0:\n                            /* remove */\n                            return false;\n                        case 1:\n                            /* add */\n                            return true;\n                        case 2:\n                            /* toggle */\n                            return !current;\n                        default: return false;\n                    }\n                    return false;\n                };\n\n                if (prop == HYPRATOMS[\"_NET_WM_STATE_FULLSCREEN\"])\n                    XSURF->m_state.requestsFullscreen = updateState(action, XSURF->m_fullscreen);\n                if (prop == HYPRATOMS[\"_NET_WM_STATE_HIDDEN\"])\n                    XSURF->m_state.requestsMinimize = updateState(action, XSURF->m_minimized);\n                if (prop == HYPRATOMS[\"_NET_WM_STATE_MAXIMIZED_VERT\"] || prop == HYPRATOMS[\"_NET_WM_STATE_MAXIMIZED_HORZ\"])\n                    XSURF->m_state.requestsMaximize = updateState(action, XSURF->m_maximized);\n            }\n\n            XSURF->m_events.stateChanged.emit();\n        }\n    } else if (e->type == HYPRATOMS[\"WM_CHANGE_STATE\"]) {\n        int state = e->data.data32[0];\n        if (state == XCB_ICCCM_WM_STATE_ICONIC || state == XCB_ICCCM_WM_STATE_WITHDRAWN)\n            XSURF->m_state.requestsMinimize = true;\n        else if (state == XCB_ICCCM_WM_STATE_NORMAL)\n            XSURF->m_state.requestsMinimize = false;\n        XSURF->m_events.stateChanged.emit();\n    } else if (e->type == HYPRATOMS[\"_NET_ACTIVE_WINDOW\"]) {\n        XSURF->m_events.activate.emit();\n    } else if (e->type == HYPRATOMS[\"XdndStatus\"]) {\n        if (m_dndDataOffers.empty() || !m_dndDataOffers.at(0)->getSource()) {\n            Log::logger->log(Log::TRACE, \"[xwm] Rejecting XdndStatus message: nothing to get\");\n            return;\n        }\n\n        xcb_client_message_data_t* data     = &e->data;\n        const bool                 ACCEPTED = data->data32[1] & 1;\n\n        if (ACCEPTED)\n            m_dndDataOffers.at(0)->getSource()->accepted(\"\");\n\n        Log::logger->log(Log::DEBUG, \"[xwm] XdndStatus: accepted: {}\");\n    } else if (e->type == HYPRATOMS[\"XdndFinished\"]) {\n        if (m_dndDataOffers.empty() || !m_dndDataOffers.at(0)->getSource()) {\n            Log::logger->log(Log::TRACE, \"[xwm] Rejecting XdndFinished message: nothing to get\");\n            return;\n        }\n\n        m_dndDataOffers.at(0)->getSource()->sendDndFinished();\n\n        Log::logger->log(Log::DEBUG, \"[xwm] XdndFinished\");\n    } else {\n        Log::logger->log(Log::TRACE, \"[xwm] Unhandled message prop {} -> {}\", e->type, propName);\n        return;\n    }\n\n    Log::logger->log(Log::TRACE, \"[xwm] Handled message prop {} -> {}\", e->type, propName);\n}\n\nvoid CXWM::handleFocusIn(xcb_focus_in_event_t* e) {\n    if (e->mode == XCB_NOTIFY_MODE_GRAB || e->mode == XCB_NOTIFY_MODE_UNGRAB || e->detail == XCB_NOTIFY_DETAIL_POINTER)\n        return;\n\n    const auto XSURF = windowForXID(e->event);\n\n    if (!XSURF)\n        return;\n\n    if (m_focusedSurface && m_focusedSurface->m_pid == XSURF->m_pid && e->sequence - m_lastFocusSeq <= 255)\n        focusWindow(XSURF);\n    else\n        focusWindow(m_focusedSurface.lock());\n}\n\nvoid CXWM::handleFocusOut(xcb_focus_out_event_t* e) {\n    Log::logger->log(Log::TRACE, \"[xwm] focusOut mode={}, detail={}, event={}\", e->mode, e->detail, e->event);\n\n    const auto XSURF = windowForXID(e->event);\n\n    if (!XSURF)\n        return;\n\n    Log::logger->log(Log::TRACE, \"[xwm] focusOut for {} {} {} surface {}\", XSURF->m_mapped ? \"mapped\" : \"unmapped\", XSURF->m_fullscreen ? \"fullscreen\" : \"windowed\",\n                     XSURF == m_focusedSurface ? \"focused\" : \"unfocused\", XSURF->m_state.title);\n\n    // do something?\n}\n\nvoid CXWM::sendWMMessage(SP<CXWaylandSurface> surf, xcb_client_message_data_t* data, uint32_t mask) {\n    xcb_client_message_event_t event = {\n        .response_type = XCB_CLIENT_MESSAGE,\n        .format        = 32,\n        .sequence      = 0,\n        .window        = surf->m_xID,\n        .type          = HYPRATOMS[\"WM_PROTOCOLS\"],\n        .data          = *data,\n    };\n\n    xcb_send_event(getConnection(), 0, surf->m_xID, mask, rc<const char*>(&event));\n    xcb_flush(getConnection());\n}\n\nvoid CXWM::focusWindow(SP<CXWaylandSurface> surf) {\n    if (surf == m_focusedSurface)\n        return;\n\n    m_focusedSurface = surf;\n\n    // send state to all toplevel surfaces, sometimes we might lose some\n    // that could still stick with the focused atom\n    for (auto const& s : m_mappedSurfaces) {\n        if (!s || s->m_overrideRedirect)\n            continue;\n\n        sendState(s.lock());\n    }\n\n    if (!surf) {\n        xcb_set_input_focus_checked(getConnection(), XCB_INPUT_FOCUS_POINTER_ROOT, XCB_NONE, XCB_CURRENT_TIME);\n        return;\n    }\n\n    if (surf->m_overrideRedirect)\n        return;\n\n    xcb_client_message_data_t msg = {{0}};\n    msg.data32[0]                 = HYPRATOMS[\"WM_TAKE_FOCUS\"];\n    msg.data32[1]                 = XCB_TIME_CURRENT_TIME;\n\n    if (surf->m_hints && !surf->m_hints->input)\n        sendWMMessage(surf, &msg, XCB_EVENT_MASK_NO_EVENT);\n    else {\n        sendWMMessage(surf, &msg, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT);\n\n        xcb_void_cookie_t cookie = xcb_set_input_focus(getConnection(), XCB_INPUT_FOCUS_POINTER_ROOT, surf->m_xID, XCB_CURRENT_TIME);\n        m_lastFocusSeq           = cookie.sequence;\n    }\n}\n\nvoid CXWM::handleError(xcb_value_error_t* e) {\n    const char* major_name = xcb_errors_get_name_for_major_code(m_errors, e->major_opcode);\n    if (!major_name) {\n        Log::logger->log(Log::ERR, \"xcb error happened, but could not get major name\");\n        return;\n    }\n\n    const char* minor_name = xcb_errors_get_name_for_minor_code(m_errors, e->major_opcode, e->minor_opcode);\n\n    const char* extension;\n    const char* error_name = xcb_errors_get_name_for_error(m_errors, e->error_code, &extension);\n    if (!error_name) {\n        Log::logger->log(Log::ERR, \"xcb error happened, but could not get error name\");\n        return;\n    }\n\n    Log::logger->log(Log::ERR, \"[xwm] xcb error: {} ({}), code {} ({}), seq {}, val {}\", major_name, minor_name ? minor_name : \"no minor\", error_name,\n                     extension ? extension : \"no extension\", e->sequence, e->bad_value);\n}\n\nvoid CXWM::selectionSendNotify(xcb_selection_request_event_t* e, bool success) {\n    xcb_selection_notify_event_t selection_notify = {\n        .response_type = XCB_SELECTION_NOTIFY,\n        .sequence      = 0,\n        .time          = e->time,\n        .requestor     = e->requestor,\n        .selection     = e->selection,\n        .target        = e->target,\n        .property      = success ? e->property : sc<uint32_t>(XCB_ATOM_NONE),\n    };\n\n    xcb_send_event(getConnection(), 0, e->requestor, XCB_EVENT_MASK_NO_EVENT, rc<const char*>(&selection_notify));\n    xcb_flush(getConnection());\n}\n\nxcb_atom_t CXWM::mimeToAtom(const std::string& mime) {\n    if (mime == \"text/plain;charset=utf-8\")\n        return HYPRATOMS[\"UTF8_STRING\"];\n    if (mime == \"text/plain\")\n        return HYPRATOMS[\"TEXT\"];\n\n    xcb_intern_atom_cookie_t             cookie = xcb_intern_atom(getConnection(), 0, mime.length(), mime.c_str());\n    XCBReplyPtr<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(getConnection(), cookie, nullptr));\n    if (!reply.get())\n        return XCB_ATOM_NONE;\n    xcb_atom_t atom = reply->atom;\n\n    return atom;\n}\n\nstd::string CXWM::mimeFromAtom(xcb_atom_t atom) {\n    if (atom == HYPRATOMS[\"UTF8_STRING\"])\n        return \"text/plain;charset=utf-8\";\n    if (atom == HYPRATOMS[\"TEXT\"])\n        return \"text/plain\";\n\n    xcb_get_atom_name_cookie_t             cookie = xcb_get_atom_name(getConnection(), atom);\n    XCBReplyPtr<xcb_get_atom_name_reply_t> reply(xcb_get_atom_name_reply(getConnection(), cookie, nullptr));\n    if (!reply)\n        return \"INVALID\";\n    size_t      len = xcb_get_atom_name_name_length(reply.get());\n    char*       buf = xcb_get_atom_name_name(reply.get()); // not a C string\n    std::string SZNAME{buf, len};\n    return SZNAME;\n}\n\nvoid CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) {\n    Log::logger->log(Log::TRACE, \"[xwm] Selection notify for {} prop {} target {}\", e->selection, e->property, e->target);\n\n    SXSelection* sel = getSelection(e->selection);\n\n    if (e->property == XCB_ATOM_NONE) {\n        auto it = std::ranges::find_if(sel->transfers, [](const auto& t) { return !t->propertyReply; });\n        if (it != sel->transfers.end()) {\n            Log::logger->log(Log::TRACE, \"[xwm] converting selection failed\");\n            sel->transfers.erase(it);\n        }\n    } else if (e->target == HYPRATOMS[\"TARGETS\"]) {\n        if (!m_focusedSurface) {\n            Log::logger->log(Log::TRACE, \"[xwm] denying access to write to clipboard because no X client is in focus\");\n            return;\n        }\n\n        setClipboardToWayland(*sel);\n    } else if (!sel->transfers.empty())\n        getTransferData(*sel);\n}\n\nbool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) {\n    for (auto* sel : {&m_clipboard, &m_primarySelection, &m_dndSelection}) {\n        auto it = std::ranges::find_if(sel->transfers, [e](const auto& t) { return t->incomingWindow == e->window; });\n        if (it == sel->transfers.end())\n            continue;\n\n        auto& transfer = *it;\n\n        if (e->state == XCB_PROPERTY_NEW_VALUE) {\n            if (!transfer->incremental) {\n                getTransferData(*sel);\n                return true;\n            }\n\n            if (!transfer->getIncomingSelectionProp(true) || xcb_get_property_value_length(transfer->propertyReply) == 0) {\n                sel->transfers.erase(it);\n                return true;\n            }\n\n            int result = sel->onWrite();\n\n            if (result == 1 && !transfer->eventSource) {\n                transfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, transfer->wlFD.get(), WL_EVENT_WRITABLE, ::writeDataSource, sel);\n            }\n        } else if (e->state == XCB_PROPERTY_DELETE) {\n            getTransferData(*sel);\n        }\n        return true;\n    }\n\n    return false;\n}\n\nSXSelection* CXWM::getSelection(xcb_atom_t atom) {\n    if (atom == HYPRATOMS[\"CLIPBOARD\"])\n        return &m_clipboard;\n    else if (atom == HYPRATOMS[\"PRIMARY\"])\n        return &m_primarySelection;\n    else if (atom == HYPRATOMS[\"XdndSelection\"])\n        return &m_dndSelection;\n\n    return nullptr;\n}\n\nvoid CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) {\n    Log::logger->log(Log::TRACE, \"[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}\", e->selection, e->property, e->target, e->time, e->requestor,\n                     e->selection);\n\n    SXSelection* sel = getSelection(e->selection);\n\n    if (!sel) {\n        Log::logger->log(Log::ERR, \"[xwm] No selection\");\n        selectionSendNotify(e, false);\n        return;\n    }\n\n    if (e->selection == HYPRATOMS[\"CLIPBOARD_MANAGER\"]) {\n        selectionSendNotify(e, true);\n        return;\n    }\n\n    if (sel->window != e->owner && e->time != XCB_CURRENT_TIME && e->time < sel->timestamp) {\n        Log::logger->log(Log::ERR, \"[xwm] outdated selection request. Time {} < {}\", e->time, sel->timestamp);\n        selectionSendNotify(e, false);\n        return;\n    }\n\n    if (!g_pSeatManager->m_state.keyboardFocusResource || g_pSeatManager->m_state.keyboardFocusResource->client() != g_pXWayland->m_server->m_xwaylandClient) {\n        Log::logger->log(Log::TRACE, \"[xwm] Ignoring clipboard access: xwayland not in focus\");\n        selectionSendNotify(e, false);\n        return;\n    }\n\n    if (e->target == HYPRATOMS[\"TARGETS\"]) {\n        // send mime types\n        std::vector<std::string> mimes;\n        if (sel == &m_clipboard && g_pSeatManager->m_selection.currentSelection)\n            mimes = g_pSeatManager->m_selection.currentSelection->mimes();\n        else if (sel == &m_dndSelection && !m_dndDataOffers.empty() && m_dndDataOffers.at(0)->m_source)\n            mimes = m_dndDataOffers.at(0)->m_source->mimes();\n\n        if (mimes.empty())\n            Log::logger->log(Log::WARN, \"[xwm] WARNING: No mimes in TARGETS?\");\n\n        std::vector<xcb_atom_t> atoms;\n        // reserve to avoid reallocations\n        atoms.reserve(mimes.size() + 2);\n        atoms.push_back(HYPRATOMS[\"TIMESTAMP\"]);\n        atoms.push_back(HYPRATOMS[\"TARGETS\"]);\n\n        for (auto const& m : mimes) {\n            atoms.push_back(mimeToAtom(m));\n        }\n\n        xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_ATOM, 32, atoms.size(), atoms.data());\n        selectionSendNotify(e, true);\n    } else if (e->target == HYPRATOMS[\"TIMESTAMP\"]) {\n        xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_INTEGER, 32, 1, &sel->timestamp);\n        selectionSendNotify(e, true);\n    } else if (e->target == HYPRATOMS[\"DELETE\"]) {\n        selectionSendNotify(e, true);\n    } else {\n        std::string mime = mimeFromAtom(e->target);\n\n        if (mime == \"INVALID\") {\n            Log::logger->log(Log::DEBUG, \"[xwm] Ignoring clipboard access: invalid mime atom {}\", e->target);\n            selectionSendNotify(e, false);\n            return;\n        }\n\n        if (!sel->sendData(e, mime)) {\n            Log::logger->log(Log::DEBUG, \"[xwm] Failed to send selection :(\");\n            selectionSendNotify(e, false);\n            return;\n        }\n    }\n}\n\nbool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) {\n    Log::logger->log(Log::TRACE, \"[xwm] Selection xfixes notify for {}\", e->selection);\n\n    // IMPORTANT: mind the g_pSeatManager below\n    SXSelection* sel = getSelection(e->selection);\n\n    if (sel == &m_dndSelection)\n        return true;\n\n    if (e->owner == XCB_WINDOW_NONE) {\n        if (sel->owner != sel->window) {\n            if (sel == &m_clipboard)\n                g_pSeatManager->setCurrentSelection(nullptr);\n            else if (sel == &m_primarySelection)\n                g_pSeatManager->setCurrentPrimarySelection(nullptr);\n        }\n\n        sel->owner = 0;\n        return true;\n    }\n\n    sel->owner = e->owner;\n\n    if (sel->owner == sel->window) {\n        sel->timestamp = e->timestamp;\n        return true;\n    }\n\n    if (sel == &m_clipboard)\n        xcb_convert_selection(getConnection(), sel->window, HYPRATOMS[\"CLIPBOARD\"], HYPRATOMS[\"TARGETS\"], HYPRATOMS[\"_WL_SELECTION\"], e->timestamp);\n    else if (sel == &m_primarySelection)\n        xcb_convert_selection(getConnection(), sel->window, HYPRATOMS[\"PRIMARY\"], HYPRATOMS[\"TARGETS\"], HYPRATOMS[\"_WL_SELECTION\"], e->timestamp);\n    xcb_flush(getConnection());\n\n    return true;\n}\n\nbool CXWM::handleSelectionEvent(xcb_generic_event_t* e) {\n    switch (e->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {\n        case XCB_SELECTION_NOTIFY: {\n            handleSelectionNotify(rc<xcb_selection_notify_event_t*>(e));\n            return true;\n        }\n        case XCB_PROPERTY_NOTIFY: {\n            return handleSelectionPropertyNotify(rc<xcb_property_notify_event_t*>(e));\n        }\n        case XCB_SELECTION_REQUEST: {\n            handleSelectionRequest(rc<xcb_selection_request_event_t*>(e));\n            return true;\n        }\n    }\n\n    if (e->response_type - m_xfixes->first_event == XCB_XFIXES_SELECTION_NOTIFY)\n        return handleSelectionXFixesNotify(rc<xcb_xfixes_selection_notify_event_t*>(e));\n\n    return false;\n}\n\nint CXWM::onEvent(int fd, uint32_t mask) {\n\n    if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {\n        Log::logger->log(Log::ERR, \"XWayland has yeeten the xwm off?!\");\n        Log::logger->log(Log::CRIT, \"XWayland has yeeten the xwm off?!\");\n        // Attempt to create fresh instance\n        g_pEventLoopManager->doLater([]() {\n            g_pXWayland->m_wm.reset();\n            g_pXWayland->m_server.reset();\n            g_pXWayland = makeUnique<CXWayland>(true);\n        });\n        return 0;\n    }\n\n    int processedEventCount = 0;\n    using XCBEventPtr       = std::unique_ptr<xcb_generic_event_t, decltype(&free)>;\n    while (true) {\n        XCBEventPtr event(xcb_poll_for_event(getConnection()), &free);\n        if (!event)\n            break;\n\n        processedEventCount++;\n\n        if (handleSelectionEvent(event.get()))\n            continue;\n\n        switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {\n            case XCB_CREATE_NOTIFY: handleCreate(rc<xcb_create_notify_event_t*>(event.get())); break;\n            case XCB_DESTROY_NOTIFY: handleDestroy(rc<xcb_destroy_notify_event_t*>(event.get())); break;\n            case XCB_CONFIGURE_REQUEST: handleConfigureRequest(rc<xcb_configure_request_event_t*>(event.get())); break;\n            case XCB_CONFIGURE_NOTIFY: handleConfigureNotify(rc<xcb_configure_notify_event_t*>(event.get())); break;\n            case XCB_MAP_REQUEST: handleMapRequest(rc<xcb_map_request_event_t*>(event.get())); break;\n            case XCB_MAP_NOTIFY: handleMapNotify(rc<xcb_map_notify_event_t*>(event.get())); break;\n            case XCB_UNMAP_NOTIFY: handleUnmapNotify(rc<xcb_unmap_notify_event_t*>(event.get())); break;\n            case XCB_PROPERTY_NOTIFY: handlePropertyNotify(rc<xcb_property_notify_event_t*>(event.get())); break;\n            case XCB_CLIENT_MESSAGE: handleClientMessage(rc<xcb_client_message_event_t*>(event.get())); break;\n            case XCB_FOCUS_IN: handleFocusIn(rc<xcb_focus_in_event_t*>(event.get())); break;\n            case XCB_FOCUS_OUT: handleFocusOut(rc<xcb_focus_out_event_t*>(event.get())); break;\n            case 0: handleError(rc<xcb_value_error_t*>(event.get())); break;\n            default: {\n                Log::logger->log(Log::TRACE, \"[xwm] unhandled event {}\", event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK);\n            }\n        }\n    }\n\n    if (processedEventCount)\n        xcb_flush(getConnection());\n\n    return processedEventCount;\n}\n\nvoid CXWM::gatherResources() {\n    xcb_prefetch_extension_data(getConnection(), &xcb_xfixes_id);\n    xcb_prefetch_extension_data(getConnection(), &xcb_composite_id);\n    xcb_prefetch_extension_data(getConnection(), &xcb_res_id);\n\n    for (auto& ATOM : HYPRATOMS) {\n        xcb_intern_atom_cookie_t             cookie = xcb_intern_atom(getConnection(), 0, ATOM.first.length(), ATOM.first.c_str());\n        XCBReplyPtr<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(getConnection(), cookie, nullptr));\n\n        if (!reply) {\n            Log::logger->log(Log::ERR, \"[xwm] Atom failed: {}\", ATOM.first);\n            continue;\n        }\n\n        ATOM.second = reply->atom;\n    }\n\n    m_xfixes = xcb_get_extension_data(getConnection(), &xcb_xfixes_id);\n\n    if (!m_xfixes || !m_xfixes->present)\n        Log::logger->log(Log::WARN, \"XFixes not available\");\n\n    auto                                          xfixes_cookie = xcb_xfixes_query_version(getConnection(), XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION);\n    XCBReplyPtr<xcb_xfixes_query_version_reply_t> xfixes_reply(xcb_xfixes_query_version_reply(getConnection(), xfixes_cookie, nullptr));\n\n    if (xfixes_reply) {\n        Log::logger->log(Log::DEBUG, \"xfixes version: {}.{}\", xfixes_reply->major_version, xfixes_reply->minor_version);\n        m_xfixesMajor = xfixes_reply->major_version;\n    }\n\n    const auto* xresReply1 = xcb_get_extension_data(getConnection(), &xcb_res_id);\n    if (!xresReply1 || !xresReply1->present)\n        return;\n\n    auto                                       xres_cookie = xcb_res_query_version(getConnection(), XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION);\n    XCBReplyPtr<xcb_res_query_version_reply_t> xres_reply(xcb_res_query_version_reply(getConnection(), xres_cookie, nullptr));\n    if (!xres_reply)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"xres version: {}.{}\", xres_reply->server_major, xres_reply->server_minor);\n    if (xres_reply->server_major > 1 || (xres_reply->server_major == 1 && xres_reply->server_minor >= 2)) {\n        m_xres = xresReply1;\n    }\n}\n\nvoid CXWM::getVisual() {\n    xcb_depth_iterator_t      d_iter;\n    xcb_visualtype_iterator_t vt_iter;\n    xcb_visualtype_t*         visualtype;\n\n    d_iter     = xcb_screen_allowed_depths_iterator(m_screen);\n    visualtype = nullptr;\n    while (d_iter.rem > 0) {\n        if (d_iter.data->depth == 32) {\n            vt_iter    = xcb_depth_visuals_iterator(d_iter.data);\n            visualtype = vt_iter.data;\n            break;\n        }\n\n        xcb_depth_next(&d_iter);\n    }\n\n    if (visualtype == nullptr) {\n        Log::logger->log(Log::DEBUG, \"xwm: No 32-bit visualtype\");\n        return;\n    }\n\n    m_visualID = visualtype->visual_id;\n    m_colormap = xcb_generate_id(getConnection());\n    xcb_create_colormap(getConnection(), XCB_COLORMAP_ALLOC_NONE, m_colormap, m_screen->root, m_visualID);\n}\n\nvoid CXWM::getRenderFormat() {\n    auto                                               cookie = xcb_render_query_pict_formats(getConnection());\n    XCBReplyPtr<xcb_render_query_pict_formats_reply_t> reply(xcb_render_query_pict_formats_reply(getConnection(), cookie, nullptr));\n\n    if (!reply) {\n        Log::logger->log(Log::DEBUG, \"xwm: No xcb_render_query_pict_formats_reply_t reply\");\n        return;\n    }\n\n    auto                       iter   = xcb_render_query_pict_formats_formats_iterator(reply.get());\n    xcb_render_pictforminfo_t* format = nullptr;\n    while (iter.rem > 0) {\n        if (iter.data->depth == 32) {\n            format = iter.data;\n            break;\n        }\n\n        xcb_render_pictforminfo_next(&iter);\n    }\n\n    if (format == nullptr) {\n        Log::logger->log(Log::DEBUG, \"xwm: No 32-bit render format\");\n        return;\n    }\n\n    m_renderFormatID = format->id;\n}\n\nCXWM::CXWM() : m_connection(makeUnique<CXCBConnection>(g_pXWayland->m_server->m_xwmFDs[0].get())) {\n\n    if (m_connection->hasError()) {\n        Log::logger->log(Log::ERR, \"[xwm] Couldn't start, error {}\", m_connection->hasError());\n        return;\n    }\n\n    CXCBErrorContext xcbErrCtx(getConnection());\n    if (!xcbErrCtx.isValid()) {\n        Log::logger->log(Log::ERR, \"[xwm] Couldn't allocate errors context\");\n        return;\n    }\n\n    m_dndDataDevice->m_self = m_dndDataDevice;\n\n    xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(getConnection()));\n    m_screen                              = screen_iterator.data;\n\n    m_eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, g_pXWayland->m_server->m_xwmFDs[0].get(), WL_EVENT_READABLE, ::onX11Event, nullptr);\n\n    gatherResources();\n    getVisual();\n    getRenderFormat();\n\n    uint32_t values[] = {\n        XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE,\n    };\n    xcb_change_window_attributes(getConnection(), m_screen->root, XCB_CW_EVENT_MASK, values);\n\n    xcb_composite_redirect_subwindows(getConnection(), m_screen->root, XCB_COMPOSITE_REDIRECT_MANUAL);\n\n    xcb_atom_t supported[] = {\n        HYPRATOMS[\"_NET_WM_STATE\"],        HYPRATOMS[\"_NET_ACTIVE_WINDOW\"],       HYPRATOMS[\"_NET_WM_MOVERESIZE\"],           HYPRATOMS[\"_NET_WM_STATE_FOCUSED\"],\n        HYPRATOMS[\"_NET_WM_STATE_MODAL\"],  HYPRATOMS[\"_NET_WM_STATE_FULLSCREEN\"], HYPRATOMS[\"_NET_WM_STATE_MAXIMIZED_VERT\"], HYPRATOMS[\"_NET_WM_STATE_MAXIMIZED_HORZ\"],\n        HYPRATOMS[\"_NET_WM_STATE_HIDDEN\"], HYPRATOMS[\"_NET_CLIENT_LIST\"],         HYPRATOMS[\"_NET_CLIENT_LIST_STACKING\"],    HYPRATOMS[\"_NET_WORKAREA\"],\n    };\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_screen->root, HYPRATOMS[\"_NET_SUPPORTED\"], XCB_ATOM_ATOM, 32, sizeof(supported) / sizeof(*supported), supported);\n\n    setActiveWindow(XCB_WINDOW_NONE);\n    initSelection();\n\n    m_listeners.newWLSurface     = PROTO::compositor->m_events.newSurface.listen([this](const auto& surface) { onNewSurface(surface); });\n    m_listeners.newXShellSurface = PROTO::xwaylandShell->m_events.newSurface.listen([this](const auto& surface) { onNewResource(surface); });\n\n    createWMWindow();\n\n    xcb_flush(getConnection());\n}\n\nCXWM::~CXWM() {\n\n    if (m_eventSource)\n        wl_event_source_remove(m_eventSource);\n\n    for (auto const& sr : m_surfaces) {\n        sr->m_events.destroy.emit();\n    }\n}\n\nvoid CXWM::setActiveWindow(xcb_window_t window) {\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_screen->root, HYPRATOMS[\"_NET_ACTIVE_WINDOW\"], HYPRATOMS[\"WINDOW\"], 32, 1, &window);\n}\n\nvoid CXWM::createWMWindow() {\n    constexpr const char* wmName = \"Hyprland :D\";\n    m_wmWindow                   = xcb_generate_id(getConnection());\n    xcb_create_window(getConnection(), XCB_COPY_FROM_PARENT, m_wmWindow, m_screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, m_screen->root_visual, 0, nullptr);\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_wmWindow, HYPRATOMS[\"_NET_WM_NAME\"], HYPRATOMS[\"UTF8_STRING\"],\n                        8, // format\n                        strlen(wmName), wmName);\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_screen->root, HYPRATOMS[\"_NET_SUPPORTING_WM_CHECK\"], XCB_ATOM_WINDOW,\n                        32, // format\n                        1, &m_wmWindow);\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_wmWindow, HYPRATOMS[\"_NET_SUPPORTING_WM_CHECK\"], XCB_ATOM_WINDOW,\n                        32, // format\n                        1, &m_wmWindow);\n    xcb_set_selection_owner(getConnection(), m_wmWindow, HYPRATOMS[\"WM_S0\"], XCB_CURRENT_TIME);\n    xcb_set_selection_owner(getConnection(), m_wmWindow, HYPRATOMS[\"_NET_WM_CM_S0\"], XCB_CURRENT_TIME);\n}\n\nvoid CXWM::activateSurface(SP<CXWaylandSurface> surf, bool activate) {\n    if ((surf == m_focusedSurface && activate) || (surf && surf->m_overrideRedirect))\n        return;\n\n    if (!surf || (!activate && Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isX11)) {\n        setActiveWindow(XCB_WINDOW_NONE);\n        focusWindow(nullptr);\n    } else {\n        setActiveWindow(surf ? surf->m_xID : sc<uint32_t>(XCB_WINDOW_NONE));\n        focusWindow(surf);\n    }\n\n    xcb_flush(getConnection());\n}\n\nvoid CXWM::sendState(SP<CXWaylandSurface> surf) {\n    Log::logger->log(Log::TRACE, \"[xwm] sendState for {} {} {} surface {}\", surf->m_mapped ? \"mapped\" : \"unmapped\", surf->m_fullscreen ? \"fullscreen\" : \"windowed\",\n                     surf == m_focusedSurface ? \"focused\" : \"unfocused\", surf->m_state.title);\n    if (surf->m_fullscreen && surf->m_mapped && surf == m_focusedSurface)\n        surf->setWithdrawn(false); // resend normal state\n\n    if (surf->m_withdrawn) {\n        xcb_delete_property(getConnection(), surf->m_xID, HYPRATOMS[\"_NET_WM_STATE\"]);\n        return;\n    }\n\n    std::vector<uint32_t> props;\n    // reserve to avoid reallocations\n    props.reserve(6); // props below\n    if (surf->m_modal)\n        props.push_back(HYPRATOMS[\"_NET_WM_STATE_MODAL\"]);\n    if (surf->m_fullscreen)\n        props.push_back(HYPRATOMS[\"_NET_WM_STATE_FULLSCREEN\"]);\n    if (surf->m_maximized) {\n        props.push_back(HYPRATOMS[\"_NET_WM_STATE_MAXIMIZED_VERT\"]);\n        props.push_back(HYPRATOMS[\"_NET_WM_STATE_MAXIMIZED_HORZ\"]);\n    }\n    if (surf->m_minimized)\n        props.push_back(HYPRATOMS[\"_NET_WM_STATE_HIDDEN\"]);\n    if (surf == m_focusedSurface)\n        props.push_back(HYPRATOMS[\"_NET_WM_STATE_FOCUSED\"]);\n\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, surf->m_xID, HYPRATOMS[\"_NET_WM_STATE\"], XCB_ATOM_ATOM, 32, props.size(), props.data());\n}\n\nvoid CXWM::onNewSurface(SP<CWLSurfaceResource> surf) {\n    if (surf->client() != g_pXWayland->m_server->m_xwaylandClient)\n        return;\n\n    Log::logger->log(Log::DEBUG, \"[xwm] New XWayland surface at {:x}\", rc<uintptr_t>(surf.get()));\n\n    const auto WLID = surf->id();\n\n    for (auto const& sr : m_surfaces) {\n        if (sr->m_surface || sr->m_wlID != WLID)\n            continue;\n\n        associate(sr, surf);\n        return;\n    }\n\n    Log::logger->log(Log::WARN, \"[xwm] CXWM::onNewSurface: no matching xwaylandSurface\");\n}\n\nvoid CXWM::onNewResource(SP<CXWaylandSurfaceResource> resource) {\n    Log::logger->log(Log::DEBUG, \"[xwm] New XWayland resource at {:x}\", rc<uintptr_t>(resource.get()));\n\n    std::erase_if(m_shellResources, [](const auto& e) { return e.expired(); });\n    m_shellResources.emplace_back(resource);\n\n    for (auto const& surf : m_surfaces) {\n        if (surf->m_resource || surf->m_wlSerial != resource->m_serial)\n            continue;\n\n        associate(surf, resource->m_surface.lock());\n        break;\n    }\n}\n\nvoid CXWM::readWindowData(SP<CXWaylandSurface> surf) {\n    const std::array<xcb_atom_t, 9> interestingProps = {\n        XCB_ATOM_WM_CLASS,          XCB_ATOM_WM_NAME,          XCB_ATOM_WM_TRANSIENT_FOR,        HYPRATOMS[\"WM_HINTS\"],\n        HYPRATOMS[\"_NET_WM_STATE\"], HYPRATOMS[\"_NET_WM_NAME\"], HYPRATOMS[\"_NET_WM_WINDOW_TYPE\"], HYPRATOMS[\"WM_NORMAL_HINTS\"],\n        HYPRATOMS[\"WM_PROTOCOLS\"],\n    };\n\n    for (size_t i = 0; i < interestingProps.size(); i++) {\n        xcb_get_property_cookie_t             cookie = xcb_get_property(getConnection(), 0, surf->m_xID, interestingProps[i], XCB_ATOM_ANY, 0, 2048);\n        XCBReplyPtr<xcb_get_property_reply_t> reply(xcb_get_property_reply(getConnection(), cookie, nullptr));\n        if (!reply) {\n            Log::logger->log(Log::ERR, \"[xwm] Failed to get window property\");\n            continue;\n        }\n        readProp(surf, interestingProps[i], reply.get());\n    }\n}\n\nSP<CXWaylandSurface> CXWM::windowForWayland(SP<CWLSurfaceResource> surf) {\n    for (auto& s : m_surfaces) {\n        if (s->m_surface == surf)\n            return s;\n    }\n\n    return nullptr;\n}\n\nvoid CXWM::associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf) {\n    if (surf->m_surface)\n        return;\n\n    auto existing = std::ranges::find_if(m_surfaces, [wlSurf](const auto& e) { return e->m_surface == wlSurf; });\n\n    if (existing != m_surfaces.end()) {\n        Log::logger->log(Log::WARN, \"[xwm] associate() called but surface is already associated to {:x}, ignoring...\", rc<uintptr_t>(surf.get()));\n        return;\n    }\n\n    surf->m_surface = wlSurf;\n    surf->ensureListeners();\n\n    readWindowData(surf);\n\n    surf->m_events.resourceChange.emit();\n}\n\nvoid CXWM::dissociate(SP<CXWaylandSurface> surf) {\n    if (!surf->m_surface)\n        return;\n\n    if (surf->m_mapped)\n        surf->unmap();\n\n    surf->m_surface.reset();\n    surf->m_events.resourceChange.emit();\n\n    Log::logger->log(Log::DEBUG, \"Dissociate for {:x}\", rc<uintptr_t>(surf.get()));\n}\n\nvoid CXWM::updateClientList() {\n    std::vector<xcb_window_t> windows;\n    windows.reserve(m_mappedSurfaces.size());\n\n    for (auto const& s : m_mappedSurfaces) {\n        if (auto surf = s.lock(); surf)\n            windows.push_back(surf->m_xID);\n    }\n\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_screen->root, HYPRATOMS[\"_NET_CLIENT_LIST\"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data());\n\n    windows.clear();\n    windows.reserve(m_mappedSurfacesStacking.size());\n\n    for (auto const& s : m_mappedSurfacesStacking) {\n        if (auto surf = s.lock(); surf)\n            windows.push_back(surf->m_xID);\n    }\n\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_screen->root, HYPRATOMS[\"_NET_CLIENT_LIST_STACKING\"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data());\n}\n\nvoid CXWM::updateWorkArea(int x, int y, int w, int h) {\n    if (!g_pXWayland || !g_pXWayland->m_wm || !g_pXWayland->m_wm->getConnection() || !m_screen || !m_screen->root)\n        return;\n    auto connection = g_pXWayland->m_wm->getConnection();\n\n    if (w <= 0 || h <= 0) {\n        xcb_delete_property(connection, m_screen->root, HYPRATOMS[\"_NET_WORKAREA\"]);\n        xcb_flush(connection);\n        return;\n    }\n\n    uint32_t values[4] = {sc<uint32_t>(x), sc<uint32_t>(y), sc<uint32_t>(w), sc<uint32_t>(h)};\n    xcb_change_property(connection, XCB_PROP_MODE_REPLACE, m_screen->root, HYPRATOMS[\"_NET_WORKAREA\"], XCB_ATOM_CARDINAL, 32, 4, values);\n    xcb_flush(connection);\n}\n\nbool CXWM::isWMWindow(xcb_window_t w) {\n    return w == m_wmWindow || w == m_clipboard.window || w == m_dndSelection.window;\n}\n\nvoid CXWM::updateOverrideRedirect(SP<CXWaylandSurface> surf, bool overrideRedirect) {\n    if (!surf || surf->m_overrideRedirect == overrideRedirect)\n        return;\n\n    surf->m_overrideRedirect = overrideRedirect;\n}\n\nvoid CXWM::initSelection() {\n    const uint32_t windowMask = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;\n    const uint32_t xfixesMask =\n        XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;\n\n    auto createSelectionWindow = [&](xcb_window_t& window, const std::string& atomName, bool inputOnly = false) {\n        window                = xcb_generate_id(getConnection());\n        const uint16_t width  = inputOnly ? 8192 : 10;\n        const uint16_t height = inputOnly ? 8192 : 10;\n\n        xcb_create_window(getConnection(), XCB_COPY_FROM_PARENT, window, m_screen->root, 0, 0, width, height, 0,\n                          inputOnly ? XCB_WINDOW_CLASS_INPUT_ONLY : XCB_WINDOW_CLASS_INPUT_OUTPUT, m_screen->root_visual, XCB_CW_EVENT_MASK, &windowMask);\n\n        if (!inputOnly) {\n            xcb_set_selection_owner(getConnection(), window, HYPRATOMS[atomName], XCB_TIME_CURRENT_TIME);\n            xcb_xfixes_select_selection_input(getConnection(), window, HYPRATOMS[atomName], xfixesMask);\n        }\n\n        return window;\n    };\n\n    createSelectionWindow(m_clipboard.window, \"CLIPBOARD_MANAGER\");\n    createSelectionWindow(m_clipboard.window, \"CLIPBOARD\");\n    m_clipboard.listeners.setSelection        = g_pSeatManager->m_events.setSelection.listen([this] { m_clipboard.onSelection(); });\n    m_clipboard.listeners.keyboardFocusChange = g_pSeatManager->m_events.keyboardFocusChange.listen([this] { m_clipboard.onKeyboardFocus(); });\n\n    createSelectionWindow(m_primarySelection.window, \"PRIMARY\");\n    m_primarySelection.listeners.setSelection        = g_pSeatManager->m_events.setPrimarySelection.listen([this] { m_primarySelection.onSelection(); });\n    m_primarySelection.listeners.keyboardFocusChange = g_pSeatManager->m_events.keyboardFocusChange.listen([this] { m_primarySelection.onKeyboardFocus(); });\n\n    createSelectionWindow(m_dndSelection.window, \"XdndAware\", true);\n    const uint32_t xdndVersion = XDND_VERSION;\n    xcb_change_property(getConnection(), XCB_PROP_MODE_REPLACE, m_dndSelection.window, HYPRATOMS[\"XdndAware\"], XCB_ATOM_ATOM, 32, 1, &xdndVersion);\n}\n\nvoid CXWM::setClipboardToWayland(SXSelection& sel) {\n    auto source = makeShared<CXDataSource>(sel);\n    if (source->mimes().empty()) {\n        Log::logger->log(Log::ERR, \"[xwm] can't set selection: no MIMEs\");\n        return;\n    }\n\n    sel.dataSource = source;\n\n    Log::logger->log(Log::DEBUG, \"[xwm] X selection at {:x} takes {}\", rc<uintptr_t>(sel.dataSource.get()), (&sel == &m_clipboard) ? \"clipboard\" : \"primary selection\");\n\n    if (&sel == &m_clipboard)\n        g_pSeatManager->setCurrentSelection(sel.dataSource);\n    else if (&sel == &m_primarySelection)\n        g_pSeatManager->setCurrentPrimarySelection(sel.dataSource);\n}\n\nstatic int writeDataSource(int fd, uint32_t mask, void* data) {\n    auto selection = sc<SXSelection*>(data);\n    return selection->onWrite();\n}\n\nvoid CXWM::getTransferData(SXSelection& sel) {\n    Log::logger->log(Log::DEBUG, \"[xwm] getTransferData\");\n\n    auto it = std::ranges::find_if(sel.transfers, [](const auto& t) { return !t->propertyReply; });\n    if (it == sel.transfers.end()) {\n        Log::logger->log(Log::ERR, \"[xwm] No pending transfer found\");\n        return;\n    }\n\n    auto& transfer = *it;\n    if (!transfer || !transfer->incomingWindow) {\n        Log::logger->log(Log::ERR, \"[xwm] Invalid transfer state\");\n        sel.transfers.erase(it);\n        return;\n    }\n\n    if (!transfer->getIncomingSelectionProp(true)) {\n        Log::logger->log(Log::ERR, \"[xwm] Failed to get property data\");\n        sel.transfers.erase(it);\n        return;\n    }\n\n    if (!transfer->propertyReply) {\n        Log::logger->log(Log::ERR, \"[xwm] No property reply\");\n        sel.transfers.erase(it);\n        return;\n    }\n\n    if (transfer->propertyReply->type == HYPRATOMS[\"INCR\"]) {\n        transfer->incremental   = true;\n        transfer->propertyStart = 0;\n        free(transfer->propertyReply); // NOLINT(cppcoreguidelines-no-malloc)\n        transfer->propertyReply = nullptr;\n        return;\n    }\n\n    // Store window ID before onWrite() - transfer may be erased during the call\n    const xcb_window_t targetWindow = transfer->incomingWindow;\n    int                writeResult  = sel.onWrite();\n\n    if (writeResult != 1)\n        return;\n\n    // Re-find the transfer by window ID (safe after potential vector modification)\n    auto updatedIt = std::ranges::find_if(sel.transfers, [targetWindow](const auto& t) { return t->incomingWindow == targetWindow; });\n    if (updatedIt == sel.transfers.end())\n        return;\n\n    auto& updatedTransfer = *updatedIt;\n    if (!updatedTransfer)\n        return;\n\n    if (updatedTransfer->eventSource || updatedTransfer->wlFD.get() == -1)\n        return;\n\n    updatedTransfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, updatedTransfer->wlFD.get(), WL_EVENT_WRITABLE, ::writeDataSource, &sel);\n}\n\nvoid CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) {\n    if (!m_renderFormatID) {\n        Log::logger->log(Log::ERR, \"[xwm] can't set cursor: no render format\");\n        return;\n    }\n\n    if (m_cursorXID)\n        xcb_free_cursor(getConnection(), m_cursorXID);\n\n    constexpr int CURSOR_DEPTH = 32;\n\n    xcb_pixmap_t  pix = xcb_generate_id(getConnection());\n    xcb_create_pixmap(getConnection(), CURSOR_DEPTH, pix, m_screen->root, size.x, size.y);\n\n    xcb_render_picture_t pic = xcb_generate_id(getConnection());\n    xcb_render_create_picture(getConnection(), pic, pix, m_renderFormatID, 0, nullptr);\n\n    xcb_gcontext_t gc = xcb_generate_id(getConnection());\n    xcb_create_gc(getConnection(), gc, pix, 0, nullptr);\n\n    xcb_put_image(getConnection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, size.x, size.y, 0, 0, 0, CURSOR_DEPTH, stride * size.y * sizeof(uint8_t), pixData);\n    xcb_free_gc(getConnection(), gc);\n\n    m_cursorXID = xcb_generate_id(getConnection());\n    xcb_render_create_cursor(getConnection(), m_cursorXID, pic, hotspot.x, hotspot.y);\n    xcb_free_pixmap(getConnection(), pix);\n    xcb_render_free_picture(getConnection(), pic);\n\n    uint32_t values[] = {m_cursorXID};\n    xcb_change_window_attributes(getConnection(), m_screen->root, XCB_CW_CURSOR, values);\n    xcb_flush(getConnection());\n}\n\nSP<CX11DataDevice> CXWM::getDataDevice() {\n    return m_dndDataDevice;\n}\n\nSP<IDataOffer> CXWM::createX11DataOffer(SP<CWLSurfaceResource> surf, SP<IDataSource> source) {\n    auto XSURF = windowForWayland(surf);\n\n    if (!XSURF) {\n        Log::logger->log(Log::ERR, \"[xwm] No xwayland surface for destination in createX11DataOffer\");\n        return nullptr;\n    }\n\n    // invalidate old\n    g_pXWayland->m_wm->m_dndDataOffers.clear();\n\n    auto offer               = m_dndDataOffers.emplace_back(makeShared<CX11DataOffer>());\n    offer->m_self            = offer;\n    offer->m_xwaylandSurface = XSURF;\n    offer->m_source          = source;\n\n    return offer;\n}\n\nvoid SXSelection::onSelection() {\n    const bool isClipboard = this == &g_pXWayland->m_wm->m_clipboard;\n    const bool isPrimary   = this == &g_pXWayland->m_wm->m_primarySelection;\n\n    auto       currentSel     = g_pSeatManager->m_selection.currentSelection;\n    auto       currentPrimSel = g_pSeatManager->m_selection.currentPrimarySelection;\n\n    const bool isX11Clipboard = isClipboard && currentSel && currentSel->type() == DATA_SOURCE_TYPE_X11;\n    const bool isX11Primary   = isPrimary && currentPrimSel && currentPrimSel->type() == DATA_SOURCE_TYPE_X11;\n\n    if (isX11Clipboard || isX11Primary)\n        return;\n\n    auto conn = g_pXWayland->m_wm->getConnection();\n\n    if (isClipboard && currentSel) {\n        xcb_set_selection_owner(conn, g_pXWayland->m_wm->m_clipboard.window, HYPRATOMS[\"CLIPBOARD\"], XCB_TIME_CURRENT_TIME);\n        xcb_flush(conn);\n        g_pXWayland->m_wm->m_clipboard.notifyOnFocus = true;\n    } else if (isPrimary && currentPrimSel) {\n        xcb_set_selection_owner(conn, g_pXWayland->m_wm->m_primarySelection.window, HYPRATOMS[\"PRIMARY\"], XCB_TIME_CURRENT_TIME);\n        xcb_flush(conn);\n        g_pXWayland->m_wm->m_primarySelection.notifyOnFocus = true;\n    }\n}\n\nvoid SXSelection::onKeyboardFocus() {\n    if (!g_pSeatManager->m_state.keyboardFocusResource || g_pSeatManager->m_state.keyboardFocusResource->client() != g_pXWayland->m_server->m_xwaylandClient)\n        return;\n\n    if (this == &g_pXWayland->m_wm->m_clipboard && g_pXWayland->m_wm->m_clipboard.notifyOnFocus) {\n        onSelection();\n        g_pXWayland->m_wm->m_clipboard.notifyOnFocus = false;\n    } else if (this == &g_pXWayland->m_wm->m_primarySelection && g_pXWayland->m_wm->m_primarySelection.notifyOnFocus) {\n        onSelection();\n        g_pXWayland->m_wm->m_primarySelection.notifyOnFocus = false;\n    }\n}\n\nint SXSelection::onRead(int fd, uint32_t mask) {\n    auto it = std::ranges::find_if(transfers, [fd](const auto& t) { return t->wlFD.get() == fd; });\n\n    if (it == transfers.end()) {\n        Log::logger->log(Log::ERR, \"[xwm] No transfer found for fd {}\", fd);\n        return 0;\n    }\n\n    auto&        transfer = *it;\n    const size_t oldSize  = transfer->data.size();\n    transfer->data.resize(oldSize + INCR_CHUNK_SIZE);\n\n    ssize_t bytesRead = read(fd, transfer->data.data() + oldSize, INCR_CHUNK_SIZE - 1);\n\n    if (bytesRead < 0) {\n        Log::logger->log(Log::ERR, \"[xwm] readDataSource died\");\n        g_pXWayland->m_wm->selectionSendNotify(&transfer->request, false);\n        transfers.erase(it);\n        return 0;\n    }\n\n    transfer->data.resize(oldSize + bytesRead);\n\n    if (bytesRead == 0) {\n        if (transfer->data.empty()) {\n            Log::logger->log(Log::WARN, \"[xwm] Transfer ended with zero bytes — rejecting\");\n            g_pXWayland->m_wm->selectionSendNotify(&transfer->request, false);\n            transfers.erase(it);\n            return 0;\n        }\n\n        Log::logger->log(Log::DEBUG, \"[xwm] Transfer complete, total size: {}\", transfer->data.size());\n        auto conn = g_pXWayland->m_wm->getConnection();\n        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, transfer->request.requestor, transfer->request.property, transfer->request.target, 8, transfer->data.size(),\n                            transfer->data.data());\n\n        xcb_flush(conn);\n        g_pXWayland->m_wm->selectionSendNotify(&transfer->request, true);\n        transfers.erase(it);\n    } else\n        Log::logger->log(Log::DEBUG, \"[xwm] Received {} bytes, awaiting more...\", bytesRead);\n\n    return 1;\n}\n\nstatic int readDataSource(int fd, uint32_t mask, void* data) {\n    Log::logger->log(Log::DEBUG, \"[xwm] readDataSource on fd {}\", fd);\n\n    auto selection = sc<SXSelection*>(data);\n\n    return selection->onRead(fd, mask);\n}\n\nbool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) {\n    WP<IDataSource> selection;\n    if (this == &g_pXWayland->m_wm->m_clipboard)\n        selection = g_pSeatManager->m_selection.currentSelection;\n    else if (this == &g_pXWayland->m_wm->m_primarySelection)\n        selection = g_pSeatManager->m_selection.currentPrimarySelection;\n    else if (!g_pXWayland->m_wm->m_dndDataOffers.empty())\n        selection = g_pXWayland->m_wm->m_dndDataOffers.at(0)->getSource();\n\n    if (!selection) {\n        Log::logger->log(Log::ERR, \"[xwm] sendData: no selection source available\");\n        return false;\n    }\n\n    const auto MIMES = selection->mimes();\n\n    if (MIMES.empty()) {\n        Log::logger->log(Log::ERR, \"[xwm] sendData: selection source has no mimes\");\n        return false;\n    }\n\n    if (std::ranges::find(MIMES, mime) == MIMES.end()) {\n        // try to guess mime, don't just blindly send random-ass shit that the app will have no fucking\n        // clue what to do with\n        Log::logger->log(Log::ERR, \"[xwm] X client asked for MIME '{}' that this selection doesn't support, guessing.\", mime);\n\n        auto needle       = mime;\n        auto selectedMime = *MIMES.begin();\n        if (mime.contains('/'))\n            needle = mime.substr(0, mime.find('/'));\n\n        Log::logger->log(Log::TRACE, \"[xwm] X MIME needle '{}'\", needle);\n\n        if (Env::isTrace()) {\n            std::string mimeList = \"\";\n            for (const auto& m : MIMES) {\n                mimeList += \"'\" + m + \"', \";\n            }\n\n            if (!MIMES.empty())\n                mimeList = mimeList.substr(0, mimeList.size() - 2);\n\n            Log::logger->log(Log::TRACE, \"[xwm] X MIME supported: {}\", mimeList);\n        }\n\n        bool found = false;\n\n        for (const auto& m : MIMES) {\n            if (m.starts_with(needle)) {\n                selectedMime = m;\n                Log::logger->log(Log::TRACE, \"[xwm] X MIME needle found type '{}'\", m);\n                found = true;\n                break;\n            }\n        }\n\n        if (!found) {\n            for (const auto& m : MIMES) {\n                if (m.contains(needle)) {\n                    selectedMime = m;\n                    Log::logger->log(Log::TRACE, \"[xwm] X MIME needle found type '{}'\", m);\n                    found = true;\n                    break;\n                }\n            }\n        }\n\n        Log::logger->log(Log::ERR, \"[xwm] Guessed mime: '{}'. Hopefully we're right enough.\", selectedMime);\n\n        mime = selectedMime;\n    }\n\n    auto transfer     = makeUnique<SXTransfer>(*this);\n    transfer->request = *e;\n\n    int p[2];\n    if (pipe(p) == -1) {\n        Log::logger->log(Log::ERR, \"[xwm] sendData: pipe() failed\");\n        return false;\n    }\n\n    fcntl(p[0], F_SETFD, FD_CLOEXEC);\n    fcntl(p[0], F_SETFL, O_NONBLOCK);\n    fcntl(p[1], F_SETFD, FD_CLOEXEC);\n    // Do NOT set O_NONBLOCK on p[1] (wayland clients may block)\n\n    transfer->wlFD = CFileDescriptor{p[0]};\n\n    Log::logger->log(Log::DEBUG, \"[xwm] sending wayland selection to xwayland with mime {}, target {}, fds {} {}\", mime, e->target, p[0], p[1]);\n\n    selection->send(mime, CFileDescriptor{p[1]});\n\n    transfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, transfer->wlFD.get(), WL_EVENT_READABLE, ::readDataSource, this);\n\n    transfers.emplace_back(std::move(transfer));\n    return true;\n}\n\nint SXSelection::onWrite() {\n    auto it = std::ranges::find_if(transfers, [](const auto& t) { return t->propertyReply; });\n    if (it == transfers.end()) {\n        Log::logger->log(Log::ERR, \"[xwm] No transfer with property data found\");\n        return 0;\n    }\n\n    auto&   transfer  = *it;\n    char*   property  = sc<char*>(xcb_get_property_value(transfer->propertyReply));\n    int     remainder = xcb_get_property_value_length(transfer->propertyReply) - transfer->propertyStart;\n\n    ssize_t len = write(transfer->wlFD.get(), property + transfer->propertyStart, remainder);\n    if (len == -1) {\n        if (errno == EAGAIN)\n            return 1;\n        Log::logger->log(Log::ERR, \"[xwm] write died in transfer get\");\n        transfers.erase(it);\n        return 0;\n    }\n\n    if (len < remainder) {\n        transfer->propertyStart += len;\n        Log::logger->log(Log::DEBUG, \"[xwm] wl client read partially: len {}\", len);\n    } else {\n        Log::logger->log(Log::DEBUG, \"[xwm] cb transfer to wl client complete, read {} bytes\", len);\n        if (!transfer->incremental) {\n            transfers.erase(it);\n            return 0;\n        } else {\n            free(transfer->propertyReply); // NOLINT(cppcoreguidelines-no-malloc)\n            transfer->propertyReply = nullptr;\n            transfer->propertyStart = 0;\n            if (transfer->eventSource) {\n                wl_event_source_remove(transfer->eventSource);\n                transfer->eventSource = nullptr;\n            }\n            return 0;\n        }\n    }\n\n    return 1;\n}\n\nvoid SXSelection::removeTransfer(xcb_window_t window) {\n    std::erase_if(transfers, [window](const auto& t) { return t->incomingWindow == window; });\n}\n\nvoid CXWM::removeTransfersForWindow(xcb_window_t window) {\n    m_clipboard.removeTransfer(window);\n    m_primarySelection.removeTransfer(window);\n    m_dndSelection.removeTransfer(window);\n}\n\nSXTransfer::~SXTransfer() {\n    if (eventSource)\n        wl_event_source_remove(eventSource);\n    if (incomingWindow)\n        xcb_destroy_window(*g_pXWayland->m_wm->m_connection, incomingWindow);\n    if (propertyReply)\n        free(propertyReply); // NOLINT(cppcoreguidelines-no-malloc)\n}\n\nbool SXTransfer::getIncomingSelectionProp(bool erase) {\n    xcb_get_property_cookie_t cookie =\n        xcb_get_property(*g_pXWayland->m_wm->m_connection, erase, incomingWindow, HYPRATOMS[\"_WL_SELECTION\"], XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff);\n\n    propertyStart = 0;\n    propertyReply = xcb_get_property_reply(*g_pXWayland->m_wm->m_connection, cookie, nullptr);\n\n    if (!propertyReply) {\n        Log::logger->log(Log::ERR, \"[SXTransfer] couldn't get a prop reply\");\n        return false;\n    }\n\n    return true;\n}\n\n#endif\n"
  },
  {
    "path": "src/xwayland/XWM.hpp",
    "content": "#pragma once\n\n#include \"XDataSource.hpp\"\n#include \"Dnd.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../helpers/signal/Signal.hpp\"\n\n#include <xcb/xcb.h>\n#include <xcb/res.h>\n#include <xcb/xfixes.h>\n#include <xcb/composite.h>\n#include <xcb/xcb_errors.h>\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <cinttypes> // for PRIxPTR\n#include <cstdint>\n\nstruct wl_event_source;\nclass CXWaylandSurfaceResource;\nstruct SXSelection;\n\nstruct SXTransfer {\n    ~SXTransfer();\n\n    SXSelection&                   selection;\n    bool                           out = true;\n\n    bool                           incremental   = false;\n    bool                           flushOnDelete = false;\n    bool                           propertySet   = false;\n\n    Hyprutils::OS::CFileDescriptor wlFD;\n    wl_event_source*               eventSource = nullptr;\n\n    std::vector<uint8_t>           data;\n\n    xcb_selection_request_event_t  request;\n\n    int                            propertyStart  = 0;\n    xcb_get_property_reply_t*      propertyReply  = nullptr;\n    xcb_window_t                   incomingWindow = 0;\n\n    bool                           getIncomingSelectionProp(bool erase);\n};\n\nstruct SXSelection {\n    xcb_window_t     window    = 0;\n    xcb_window_t     owner     = 0;\n    xcb_timestamp_t  timestamp = 0;\n    SP<CXDataSource> dataSource;\n    bool             notifyOnFocus = false;\n\n    void             onSelection();\n    void             onKeyboardFocus();\n    bool             sendData(xcb_selection_request_event_t* e, std::string mime);\n    int              onRead(int fd, uint32_t mask);\n    int              onWrite();\n    void             removeTransfer(xcb_window_t window);\n\n    struct {\n        CHyprSignalListener setSelection;\n        CHyprSignalListener keyboardFocusChange;\n    } listeners;\n\n    std::vector<UP<SXTransfer>> transfers;\n};\n\nclass CXCBConnection {\n  public:\n    CXCBConnection(int fd) : m_connection{xcb_connect_to_fd(fd, nullptr)} {\n        ;\n    }\n\n    ~CXCBConnection() {\n        if (m_connection) {\n            Log::logger->log(Log::DEBUG, \"Disconnecting XCB connection {:x}\", rc<uintptr_t>(m_connection));\n            xcb_disconnect(m_connection);\n            m_connection = nullptr;\n        } else\n            Log::logger->log(Log::ERR, \"Double xcb_disconnect attempt\");\n    }\n\n    bool hasError() const {\n        return xcb_connection_has_error(m_connection);\n    }\n\n    operator xcb_connection_t*() const {\n        return m_connection;\n    }\n\n  private:\n    xcb_connection_t* m_connection = nullptr;\n};\n\nclass CXCBErrorContext {\n  public:\n    explicit CXCBErrorContext(xcb_connection_t* connection) {\n        if (xcb_errors_context_new(connection, &m_errors) != 0)\n            m_errors = nullptr;\n    }\n\n    ~CXCBErrorContext() {\n        if (m_errors)\n            xcb_errors_context_free(m_errors);\n    }\n\n    bool isValid() const {\n        return m_errors != nullptr;\n    }\n\n  private:\n    xcb_errors_context_t* m_errors = nullptr;\n};\n\nclass CXWM {\n  public:\n    CXWM();\n    ~CXWM();\n\n    int                onEvent(int fd, uint32_t mask);\n    SP<CX11DataDevice> getDataDevice();\n    SP<IDataOffer>     createX11DataOffer(SP<CWLSurfaceResource> surf, SP<IDataSource> source);\n    void               updateWorkArea(int x, int y, int w, int h);\n\n  private:\n    void                 setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot);\n\n    void                 gatherResources();\n    void                 getVisual();\n    void                 getRenderFormat();\n    void                 createWMWindow();\n    void                 initSelection();\n\n    void                 onNewSurface(SP<CWLSurfaceResource> surf);\n    void                 onNewResource(SP<CXWaylandSurfaceResource> resource);\n\n    void                 setActiveWindow(xcb_window_t window);\n    void                 sendState(SP<CXWaylandSurface> surf);\n    void                 focusWindow(SP<CXWaylandSurface> surf);\n    void                 activateSurface(SP<CXWaylandSurface> surf, bool activate);\n    bool                 isWMWindow(xcb_window_t w);\n    void                 updateOverrideRedirect(SP<CXWaylandSurface> surf, bool overrideRedirect);\n\n    void                 sendWMMessage(SP<CXWaylandSurface> surf, xcb_client_message_data_t* data, uint32_t mask);\n\n    SP<CXWaylandSurface> windowForXID(xcb_window_t wid);\n    SP<CXWaylandSurface> windowForWayland(SP<CWLSurfaceResource> surf);\n\n    void                 readWindowData(SP<CXWaylandSurface> surf);\n    void                 associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf);\n    void                 dissociate(SP<CXWaylandSurface> surf);\n\n    void                 updateClientList();\n\n    // event handlers\n    void         handleCreate(xcb_create_notify_event_t* e);\n    void         handleDestroy(xcb_destroy_notify_event_t* e);\n    void         handleConfigureRequest(xcb_configure_request_event_t* e);\n    void         handleConfigureNotify(xcb_configure_notify_event_t* e);\n    void         handleMapRequest(xcb_map_request_event_t* e);\n    void         handleMapNotify(xcb_map_notify_event_t* e);\n    void         handleUnmapNotify(xcb_unmap_notify_event_t* e);\n    void         handlePropertyNotify(xcb_property_notify_event_t* e);\n    void         handleClientMessage(xcb_client_message_event_t* e);\n    void         handleFocusIn(xcb_focus_in_event_t* e);\n    void         handleFocusOut(xcb_focus_out_event_t* e);\n    void         handleError(xcb_value_error_t* e);\n\n    void         removeTransfersForWindow(xcb_window_t window);\n\n    bool         handleSelectionEvent(xcb_generic_event_t* e);\n    void         handleSelectionNotify(xcb_selection_notify_event_t* e);\n    bool         handleSelectionPropertyNotify(xcb_property_notify_event_t* e);\n    void         handleSelectionRequest(xcb_selection_request_event_t* e);\n    bool         handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e);\n\n    void         selectionSendNotify(xcb_selection_request_event_t* e, bool success);\n    xcb_atom_t   mimeToAtom(const std::string& mime);\n    std::string  mimeFromAtom(xcb_atom_t atom);\n    void         setClipboardToWayland(SXSelection& sel);\n    void         getTransferData(SXSelection& sel);\n    std::string  getAtomName(uint32_t atom);\n    void         readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply);\n\n    SXSelection* getSelection(xcb_atom_t atom);\n\n    //\n    UP<CXCBConnection>                        m_connection;\n    xcb_errors_context_t*                     m_errors = nullptr;\n    xcb_screen_t*                             m_screen = nullptr;\n\n    xcb_window_t                              m_wmWindow;\n\n    wl_event_source*                          m_eventSource = nullptr;\n\n    const xcb_query_extension_reply_t*        m_xfixes      = nullptr;\n    const xcb_query_extension_reply_t*        m_xres        = nullptr;\n    int                                       m_xfixesMajor = 0;\n\n    xcb_visualid_t                            m_visualID;\n    xcb_colormap_t                            m_colormap;\n    uint32_t                                  m_cursorXID = 0;\n\n    xcb_render_pictformat_t                   m_renderFormatID;\n\n    std::vector<WP<CXWaylandSurfaceResource>> m_shellResources;\n    std::vector<SP<CXWaylandSurface>>         m_surfaces;\n    std::vector<WP<CXWaylandSurface>>         m_mappedSurfaces;         // ordered by map time\n    std::vector<WP<CXWaylandSurface>>         m_mappedSurfacesStacking; // ordered by stacking\n\n    WP<CXWaylandSurface>                      m_focusedSurface;\n    uint64_t                                  m_lastFocusSeq = 0;\n\n    SXSelection                               m_clipboard;\n    SXSelection                               m_primarySelection;\n    SXSelection                               m_dndSelection;\n    SP<CX11DataDevice>                        m_dndDataDevice = makeShared<CX11DataDevice>();\n    std::vector<SP<CX11DataOffer>>            m_dndDataOffers;\n\n    inline xcb_connection_t*                  getConnection() {\n        return m_connection ? *m_connection : nullptr;\n    }\n    struct {\n        CHyprSignalListener newWLSurface;\n        CHyprSignalListener newXShellSurface;\n    } m_listeners;\n\n    friend class CXWaylandSurface;\n    friend class CXWayland;\n    friend class CXDataSource;\n    friend class CX11DataDevice;\n    friend class CX11DataSource;\n    friend class CX11DataOffer;\n    friend class CWLDataDeviceProtocol;\n    friend struct SXSelection;\n    friend struct SXTransfer;\n};\n"
  },
  {
    "path": "src/xwayland/XWayland.cpp",
    "content": "#include \"XWayland.hpp\"\n#include \"../Compositor.hpp\"\n#include \"../debug/log/Logger.hpp\"\n#include \"../helpers/fs/FsUtils.hpp\"\n\nCXWayland::CXWayland(const bool wantsEnabled) {\n#ifndef NO_XWAYLAND\n    // Disable Xwayland and clean up if the user disabled it.\n    if (!wantsEnabled) {\n        Log::logger->log(Log::DEBUG, \"XWayland has been disabled, cleaning up...\");\n        for (auto& w : g_pCompositor->m_windows) {\n            if (!w->m_isX11)\n                continue;\n            g_pCompositor->closeWindow(w);\n        }\n        unsetenv(\"DISPLAY\");\n        m_enabled = false;\n        return;\n    }\n\n    if (!NFsUtils::executableExistsInPath(\"Xwayland\")) {\n        // If Xwayland doesn't exist, don't try to start it.\n        Log::logger->log(Log::DEBUG, \"Unable to find XWayland; not starting it.\");\n        return;\n    }\n\n    Log::logger->log(Log::DEBUG, \"Starting up the XWayland server\");\n\n    m_server = makeUnique<CXWaylandServer>();\n\n    if (!m_server->create()) {\n        Log::logger->log(Log::ERR, \"XWayland failed to start: it will not work.\");\n        return;\n    }\n\n    m_enabled = true;\n#else\n    Log::logger->log(Log::DEBUG, \"Not starting XWayland: disabled at compile time\");\n#endif\n}\n\nvoid CXWayland::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) {\n#ifndef NO_XWAYLAND\n    if (!m_wm) {\n        Log::logger->log(Log::ERR, \"Couldn't set XCursor: no XWM yet\");\n        return;\n    }\n\n    m_wm->setCursor(pixData, stride, size, hotspot);\n#endif\n}\n\nbool CXWayland::enabled() {\n    return m_enabled;\n}\n"
  },
  {
    "path": "src/xwayland/XWayland.hpp",
    "content": "#pragma once\n\n#include \"../helpers/signal/Signal.hpp\"\n#include \"../helpers/memory/Memory.hpp\"\n#include \"../macros.hpp\"\n\n#include \"XSurface.hpp\"\n\n#ifndef NO_XWAYLAND\n#include \"Server.hpp\"\n#include \"XWM.hpp\"\n#else\nclass CXWaylandServer;\nclass CXWM;\n#endif\n\nclass CXWayland {\n  public:\n    CXWayland(const bool wantsEnabled);\n\n#ifndef NO_XWAYLAND\n    UP<CXWaylandServer> m_server;\n    UP<CXWM>            m_wm;\n#endif\n    bool enabled();\n\n    void setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot);\n\n  private:\n    bool m_enabled = false;\n};\n\ninline UP<CXWayland>                             g_pXWayland;\n\ninline std::unordered_map<std::string, uint32_t> HYPRATOMS = {\n#ifndef NO_XWAYLAND\n    HYPRATOM(\"_NET_SUPPORTED\"),\n    HYPRATOM(\"_NET_SUPPORTING_WM_CHECK\"),\n    HYPRATOM(\"_NET_WM_NAME\"),\n    HYPRATOM(\"_NET_WM_VISIBLE_NAME\"),\n    HYPRATOM(\"_NET_WM_MOVERESIZE\"),\n    HYPRATOM(\"_NET_WM_STATE_STICKY\"),\n    HYPRATOM(\"_NET_WM_STATE_FULLSCREEN\"),\n    HYPRATOM(\"_NET_WM_STATE_DEMANDS_ATTENTION\"),\n    HYPRATOM(\"_NET_WM_STATE_MODAL\"),\n    HYPRATOM(\"_NET_WM_STATE_HIDDEN\"),\n    HYPRATOM(\"_NET_WM_STATE_FOCUSED\"),\n    HYPRATOM(\"_NET_WM_STATE\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_NORMAL\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_DOCK\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_DIALOG\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_UTILITY\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_TOOLBAR\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_SPLASH\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_MENU\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_POPUP_MENU\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_TOOLTIP\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_NOTIFICATION\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_COMBO\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_DND\"),\n    HYPRATOM(\"_NET_WM_WINDOW_TYPE_DESKTOP\"),\n    HYPRATOM(\"_NET_WM_STATE_MAXIMIZED_HORZ\"),\n    HYPRATOM(\"_NET_WM_STATE_MAXIMIZED_VERT\"),\n    HYPRATOM(\"_NET_WM_DESKTOP\"),\n    HYPRATOM(\"_NET_WM_STRUT_PARTIAL\"),\n    HYPRATOM(\"_NET_CLIENT_LIST\"),\n    HYPRATOM(\"_NET_CLIENT_LIST_STACKING\"),\n    HYPRATOM(\"_NET_CURRENT_DESKTOP\"),\n    HYPRATOM(\"_NET_NUMBER_OF_DESKTOPS\"),\n    HYPRATOM(\"_NET_DESKTOP_NAMES\"),\n    HYPRATOM(\"_NET_DESKTOP_VIEWPORT\"),\n    HYPRATOM(\"_NET_ACTIVE_WINDOW\"),\n    HYPRATOM(\"_NET_CLOSE_WINDOW\"),\n    HYPRATOM(\"_NET_MOVERESIZE_WINDOW\"),\n    HYPRATOM(\"_NET_WM_USER_TIME\"),\n    HYPRATOM(\"_NET_STARTUP_ID\"),\n    HYPRATOM(\"_NET_WORKAREA\"),\n    HYPRATOM(\"_NET_WM_ICON\"),\n    HYPRATOM(\"_NET_WM_CM_S0\"),\n    HYPRATOM(\"_NET_WM_PING\"),\n    HYPRATOM(\"WM_PROTOCOLS\"),\n    HYPRATOM(\"WM_HINTS\"),\n    HYPRATOM(\"WM_DELETE_WINDOW\"),\n    HYPRATOM(\"UTF8_STRING\"),\n    HYPRATOM(\"WM_STATE\"),\n    HYPRATOM(\"WM_CLIENT_LEADER\"),\n    HYPRATOM(\"WM_TAKE_FOCUS\"),\n    HYPRATOM(\"WM_NORMAL_HINTS\"),\n    HYPRATOM(\"WM_SIZE_HINTS\"),\n    HYPRATOM(\"WM_WINDOW_ROLE\"),\n    HYPRATOM(\"_NET_REQUEST_FRAME_EXTENTS\"),\n    HYPRATOM(\"_NET_FRAME_EXTENTS\"),\n    HYPRATOM(\"_MOTIF_WM_HINTS\"),\n    HYPRATOM(\"WM_CHANGE_STATE\"),\n    HYPRATOM(\"_NET_SYSTEM_TRAY_OPCODE\"),\n    HYPRATOM(\"_NET_SYSTEM_TRAY_COLORS\"),\n    HYPRATOM(\"_NET_SYSTEM_TRAY_VISUAL\"),\n    HYPRATOM(\"_NET_SYSTEM_TRAY_ORIENTATION\"),\n    HYPRATOM(\"_XEMBED_INFO\"),\n    HYPRATOM(\"MANAGER\"),\n    HYPRATOM(\"XdndSelection\"),\n    HYPRATOM(\"XdndAware\"),\n    HYPRATOM(\"XdndStatus\"),\n    HYPRATOM(\"XdndPosition\"),\n    HYPRATOM(\"XdndEnter\"),\n    HYPRATOM(\"XdndLeave\"),\n    HYPRATOM(\"XdndDrop\"),\n    HYPRATOM(\"XdndFinished\"),\n    HYPRATOM(\"XdndProxy\"),\n    HYPRATOM(\"XdndTypeList\"),\n    HYPRATOM(\"XdndActionMove\"),\n    HYPRATOM(\"XdndActionCopy\"),\n    HYPRATOM(\"XdndActionAsk\"),\n    HYPRATOM(\"XdndActionPrivate\"),\n    HYPRATOM(\"CLIPBOARD\"),\n    HYPRATOM(\"PRIMARY\"),\n    HYPRATOM(\"_WL_SELECTION\"),\n    HYPRATOM(\"CLIPBOARD_MANAGER\"),\n    HYPRATOM(\"WINDOW\"),\n    HYPRATOM(\"WM_S0\"),\n    HYPRATOM(\"WL_SURFACE_ID\"),\n    HYPRATOM(\"WL_SURFACE_SERIAL\"),\n    HYPRATOM(\"TARGETS\"),\n    HYPRATOM(\"TIMESTAMP\"),\n    HYPRATOM(\"DELETE\"),\n    HYPRATOM(\"TEXT\"),\n    HYPRATOM(\"INCR\"),\n#endif\n};\n"
  },
  {
    "path": "start/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.19)\n\nproject(start-hyprland DESCRIPTION \"Hyprland watchdog binary\")\n\ninclude(GNUInstallDirs)\n\nset(CMAKE_CXX_STANDARD 26)\nset(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)\n\nfind_package(PkgConfig REQUIRED)\n\npkg_check_modules(starthyprland_deps REQUIRED IMPORTED_TARGET hyprutils>=0.10.3)\n\nfind_package(glaze 7...<8 QUIET)\nif (NOT glaze_FOUND)\n    set(GLAZE_VERSION v7.2.0)\n    message(STATUS \"start: glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent\")\n    include(FetchContent)\n    FetchContent_Declare(\n        glaze\n        GIT_REPOSITORY https://github.com/stephenberry/glaze.git\n        GIT_TAG ${GLAZE_VERSION}\n        GIT_SHALLOW TRUE\n        EXCLUDE_FROM_ALL\n    )\n    FetchContent_MakeAvailable(glaze)\nendif()\n\nfile(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS \"src/*.cpp\")\n\nadd_executable(start-hyprland ${SRCFILES})\n\ntarget_link_libraries(start-hyprland PUBLIC PkgConfig::starthyprland_deps)\ntarget_link_libraries(start-hyprland PUBLIC glaze::glaze)\n\ninstall(TARGETS start-hyprland)\n\nif(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)\n  set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)\nendif()\n"
  },
  {
    "path": "start/src/core/Instance.cpp",
    "content": "#include \"Instance.hpp\"\n#include \"State.hpp\"\n#include \"../helpers/Logger.hpp\"\n#include \"../helpers/Nix.hpp\"\n\n#include <cstdlib>\n#include <cstring>\n#include <sys/poll.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <ranges>\n#include <string_view>\n\n#if defined(__linux__)\n#include <sys/prctl.h>\n#elif defined(__FreeBSD__)\n#include <signal.h>\n#include <sys/procctl.h>\n#endif\n\n#include <hyprutils/os/Process.hpp>\n\nusing namespace Hyprutils::OS;\nusing namespace std::string_literals;\n\n//\nvoid CHyprlandInstance::runHyprlandThread(bool safeMode) {\n    std::vector<std::string> argsStd;\n    argsStd.emplace_back(\"--watchdog-fd\");\n    argsStd.emplace_back(std::format(\"{}\", m_toHlPid.get()));\n    if (safeMode)\n        argsStd.emplace_back(\"--safe-mode\");\n\n    for (const auto& a : g_state->rawArgvNoBinPath) {\n        argsStd.emplace_back(a);\n    }\n\n    // spawn a process manually. Hyprutils' Async is detached, while Sync redirects stdout\n    // TODO: make Sync respect fds?\n\n    std::vector<char*> args = {strdup(g_state->customPath.value_or(\"Hyprland\").c_str())};\n    for (const auto& a : argsStd) {\n        args.emplace_back(strdup(a.c_str()));\n    }\n    args.emplace_back(nullptr);\n\n    int forkRet = fork();\n    if (forkRet == 0) {\n        // Make hyprland die on our SIGKILL\n#if defined(__linux__)\n        prctl(PR_SET_PDEATHSIG, SIGKILL);\n#elif defined(__FreeBSD__)\n        int sig = SIGKILL;\n        procctl(P_PID, getpid(), PROC_PDEATHSIG_CTL, &sig);\n#endif\n\n        if (Nix::shouldUseNixGL()) {\n            argsStd.insert(argsStd.begin(), g_state->customPath.value_or(\"Hyprland\"));\n            args.insert(args.begin(), strdup(argsStd.front().c_str()));\n            execvp(\"nixGL\", args.data());\n        } else\n            execvp(g_state->customPath.value_or(\"Hyprland\").c_str(), args.data());\n\n        g_logger->log(Hyprutils::CLI::LOG_ERR, \"fork(): execvp failed: {}\", strerror(errno));\n        std::fflush(stdout);\n        exit(1);\n    } else\n        m_hlPid = forkRet;\n\n    m_hlThread = std::thread([this] {\n        while (true) {\n            int status = 0;\n            int ret    = waitpid(m_hlPid, &status, 0);\n            if (ret == -1) {\n                g_logger->log(Hyprutils::CLI::LOG_ERR, \"Couldn't waitpid for hyprland: {}\", strerror(errno));\n                break;\n            }\n\n            if (WIFEXITED(status))\n                break;\n        }\n\n        write(m_wakeupWrite.get(), \"vax\", 3);\n\n        std::fflush(stdout);\n        std::fflush(stderr);\n    });\n}\n\nvoid CHyprlandInstance::forceQuit() {\n    m_hyprlandExiting = true;\n    kill(m_hlPid, SIGTERM); // gracefully, can get stuck but it's unlikely\n\n    m_hlThread.join(); // needs this otherwise can crash\n}\n\nvoid CHyprlandInstance::clearFd(const Hyprutils::OS::CFileDescriptor& fd) {\n    static std::array<char, 1024> buf;\n    read(fd.get(), buf.data(), 1023);\n}\n\nvoid CHyprlandInstance::dispatchHyprlandEvent() {\n    std::string                   recvd = \"\";\n    static std::array<char, 4096> buf;\n    ssize_t                       n = read(m_fromHlPid.get(), buf.data(), 4096);\n    if (n < 0) {\n        g_logger->log(Hyprutils::CLI::LOG_ERR, \"Failed dispatching hl events\");\n        return;\n    }\n\n    recvd.append(buf.data(), n);\n\n    if (recvd.empty())\n        return;\n\n    for (const auto& s : std::views::split(recvd, '\\n')) {\n        const std::string_view sv = std::string_view{s};\n        if (sv == \"vax\") {\n            // init passed\n            m_hyprlandInitialized = true;\n            continue;\n        }\n\n        if (sv == \"end\") {\n            // exiting\n            m_hyprlandExiting = true;\n            continue;\n        }\n    }\n}\n\nbool CHyprlandInstance::run(bool safeMode) {\n    int pipefds[2];\n    pipe(pipefds);\n\n    m_fromHlPid = CFileDescriptor{pipefds[0]};\n    m_toHlPid   = CFileDescriptor{pipefds[1]};\n\n    pipe(pipefds);\n\n    m_wakeupRead  = CFileDescriptor{pipefds[0]};\n    m_wakeupWrite = CFileDescriptor{pipefds[1]};\n\n    runHyprlandThread(safeMode);\n\n    pollfd pollfds[2] = {\n        {\n            .fd      = m_wakeupRead.get(),\n            .events  = POLLIN,\n            .revents = 0,\n        },\n        {\n            .fd      = m_fromHlPid.get(),\n            .events  = POLLIN,\n            .revents = 0,\n        },\n    };\n\n    while (true) {\n        int ret = poll(pollfds, 2, -1);\n\n        if (ret < 0) {\n            g_logger->log(Hyprutils::CLI::LOG_ERR, \"poll() failed, exiting\");\n            exit(1);\n        }\n\n        if (pollfds[1].revents & POLLIN) {\n            g_logger->log(Hyprutils::CLI::LOG_DEBUG, \"got an event from hyprland\");\n            dispatchHyprlandEvent();\n            continue;\n        }\n\n        if (pollfds[0].revents & POLLIN) {\n            g_logger->log(Hyprutils::CLI::LOG_DEBUG, \"hyprland exit, breaking poll, checking state\");\n            clearFd(m_wakeupRead);\n            break;\n        }\n    }\n\n    m_hlThread.join();\n\n    return !m_hyprlandInitialized || m_hyprlandExiting;\n}\n"
  },
  {
    "path": "start/src/core/Instance.hpp",
    "content": "#pragma once\n\n#include <hyprutils/os/FileDescriptor.hpp>\n#include <thread>\n\n#include \"../helpers/Memory.hpp\"\n\nclass CHyprlandInstance {\n  public:\n    CHyprlandInstance()  = default;\n    ~CHyprlandInstance() = default;\n\n    CHyprlandInstance(const CHyprlandInstance&) = delete;\n    CHyprlandInstance(CHyprlandInstance&)       = delete;\n    CHyprlandInstance(CHyprlandInstance&&)      = delete;\n\n    bool run(bool safeMode = false); // if returns false, restart.\n    void forceQuit();\n\n  private:\n    void                           runHyprlandThread(bool safeMode);\n    void                           clearFd(const Hyprutils::OS::CFileDescriptor& fd);\n    void                           dispatchHyprlandEvent();\n\n    int                            m_hlPid = -1;\n\n    Hyprutils::OS::CFileDescriptor m_fromHlPid, m_toHlPid;\n    Hyprutils::OS::CFileDescriptor m_wakeupRead, m_wakeupWrite;\n\n    bool                           m_hyprlandInitialized = false;\n    bool                           m_hyprlandExiting     = false;\n\n    std::thread                    m_hlThread;\n};\n\ninline UP<CHyprlandInstance> g_instance;\n"
  },
  {
    "path": "start/src/core/State.hpp",
    "content": "#pragma once\n\n#include \"../helpers/Memory.hpp\"\n\n#include <span>\n#include <optional>\n\nstruct SState {\n    std::span<const char*>     rawArgvNoBinPath;\n    std::optional<std::string> customPath;\n    bool                       noNixGl    = false;\n    bool                       forceNixGl = false;\n};\n\ninline UP<SState> g_state = makeUnique<SState>();"
  },
  {
    "path": "start/src/helpers/Logger.hpp",
    "content": "#pragma once\n\n#include <hyprutils/cli/Logger.hpp>\n\n#include \"Memory.hpp\"\n\n// we do this to add a from start-hyprland to the logs\ninline UP<Hyprutils::CLI::CLogger>           g_loggerMain = makeUnique<Hyprutils::CLI::CLogger>();\ninline UP<Hyprutils::CLI::CLoggerConnection> g_logger;\n"
  },
  {
    "path": "start/src/helpers/Memory.hpp",
    "content": "#pragma once\n\n#include <hyprutils/memory/WeakPtr.hpp>\n#include <hyprutils/memory/Atomic.hpp>\n\nusing namespace Hyprutils::Memory;\n\ntemplate <typename T>\nusing SP = Hyprutils::Memory::CSharedPointer<T>;\ntemplate <typename T>\nusing WP = Hyprutils::Memory::CWeakPointer<T>;\ntemplate <typename T>\nusing UP = Hyprutils::Memory::CUniquePointer<T>;\ntemplate <typename T>\nusing ASP = Hyprutils::Memory::CAtomicSharedPointer<T>;\n"
  },
  {
    "path": "start/src/helpers/Nix.cpp",
    "content": "#include \"Nix.hpp\"\n\n#include \"Logger.hpp\"\n#include \"../core/State.hpp\"\n\n#include <filesystem>\n#include <algorithm>\n#include <hyprutils/string/VarList2.hpp>\n#include <hyprutils/string/String.hpp>\n#include <hyprutils/os/Process.hpp>\n#include <hyprutils/os/File.hpp>\n\n#include <glaze/glaze.hpp>\n\nusing namespace Hyprutils::String;\nusing namespace Hyprutils::OS;\n\nusing namespace Hyprutils::File;\n\nstatic std::optional<std::string> getFromEtcOsRelease(const std::string_view& sv) {\n    static std::string content = \"\";\n    static bool        once    = true;\n\n    if (once) {\n        once = false;\n\n        auto read = readFileAsString(\"/etc/os-release\");\n        content   = read.value_or(\"\");\n    }\n\n    static CVarList2 vars(std::move(content), 0, '\\n', true);\n\n    for (const auto& v : vars) {\n        if (v.starts_with(sv) && v.contains('=')) {\n            // found\n            auto value = trim(v.substr(v.find('=') + 1));\n\n            if (value.back() == value.front() && value.back() == '\"')\n                value = value.substr(1, value.size() - 2);\n\n            return std::string{value};\n        }\n    }\n\n    return std::nullopt;\n}\n\nstatic bool executableExistsInPath(const std::string& exe) {\n    const char* PATHENV = std::getenv(\"PATH\");\n    if (!PATHENV)\n        return false;\n\n    CVarList2       paths(PATHENV, 0, ':', true);\n    std::error_code ec;\n\n    for (const auto& PATH : paths) {\n        std::filesystem::path candidate = std::filesystem::path(PATH) / exe;\n        if (!std::filesystem::exists(candidate, ec) || ec)\n            continue;\n        if (!std::filesystem::is_regular_file(candidate, ec) || ec)\n            continue;\n        auto perms = std::filesystem::status(candidate, ec).permissions();\n        if (ec)\n            continue;\n        if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)\n            return true;\n    }\n\n    return false;\n}\n\nstd::expected<void, std::string> Nix::nixEnvironmentOk() {\n    if (!shouldUseNixGL())\n        return {};\n\n    if (!executableExistsInPath(\"nixGL\"))\n        return std::unexpected(\n            \"Hyprland was installed using Nix, but you're not on NixOS. This requires nixGL to be installed as well.\\nYou can install nixGL by running \\\"nix profile install \"\n            \"github:guibou/nixGL --impure\\\" in your terminal.\");\n\n    return {};\n}\n\nbool Nix::shouldUseNixGL() {\n    if (g_state->forceNixGl)\n        return true;\n\n    if (g_state->noNixGl)\n        return false;\n\n    // check if installed hyprland is nix'd\n    CProcess proc(\"Hyprland\", {\"--version-json\"});\n    if (!proc.runSync()) {\n        g_logger->log(Hyprutils::CLI::LOG_ERR, \"failed to obtain hyprland version string\");\n        return false;\n    }\n\n    auto json = glz::read_json<glz::generic>(proc.stdOut());\n    if (!json) {\n        g_logger->log(Hyprutils::CLI::LOG_ERR, \"failed to obtain hyprland version string (bad json)\");\n        return false;\n    }\n\n    const auto FLAGS  = (*json)[\"flags\"].get_array();\n    const bool IS_NIX = std::ranges::any_of(FLAGS, [](const auto& e) { return e.get_string() == std::string_view{\"nix\"}; });\n\n    if (IS_NIX) {\n        const auto NAME = getFromEtcOsRelease(\"NAME\");\n\n        // Hyprland is nix: recommend nixGL iff !NIX && !NIX-OPENGL\n\n        if (*NAME == \"NixOS\")\n            return false;\n\n        std::error_code ec;\n\n        if (std::filesystem::exists(\"/run/opengl-driver\", ec) && !ec) // NOLINTNEXTLINE\n            return false;\n\n        // we are not on nix / no nix opengl driver\n        return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "start/src/helpers/Nix.hpp",
    "content": "#pragma once\n\n#include <expected>\n#include <string>\n\nnamespace Nix {\n    std::expected<void, std::string> nixEnvironmentOk();\n    bool                             shouldUseNixGL();\n};"
  },
  {
    "path": "start/src/main.cpp",
    "content": "#include <csignal>\n#include <cstring>\n#include <print>\n\n#include \"helpers/Logger.hpp\"\n#include \"helpers/Nix.hpp\"\n#include \"core/State.hpp\"\n#include \"core/Instance.hpp\"\n\nusing namespace Hyprutils::CLI;\n\n#define ASSERT(expr)                                                                                                                                                               \\\n    if (!(expr)) {                                                                                                                                                                 \\\n        g_logger->log(LOG_CRIT, \"Failed assertion at line {} in {}: {} was false\", __LINE__,                                                                                       \\\n                      ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find(\"/src/\") + 1); })(), #expr);                                 \\\n        std::abort();                                                                                                                                                              \\\n    }\n\nconstexpr const char* HELP_INFO = R\"#(start-hyprland - A binary to properly start Hyprland via a watchdog process.\nAny arguments after -- are passed to Hyprland. For Hyprland help, run start-hyprland -- --help or Hyprland --help\n\nAdditional arguments for start-hyprland:\n --path [path]       -> Override Hyprland path\n --no-nixgl          -> Force disable nixGL\n --force-nixgl       -> Force enable nixGL\n)#\";\n\n//\nstatic void onSignal(int sig) {\n    if (!g_instance)\n        return;\n\n    g_instance->forceQuit();\n    g_instance.reset();\n\n    exit(0);\n}\n\nstatic void terminateChildOnSignal(int signal) {\n    struct sigaction sa;\n    sa.sa_handler = onSignal;\n    sigemptyset(&sa.sa_mask);\n    sa.sa_flags = SA_RESTART;\n    int ret     = sigaction(signal, &sa, nullptr);\n    if (ret != 0)\n        g_logger->log(Hyprutils::CLI::LOG_WARN, \"Failed to set up handler for signal {}: {}\", signal, strerror(errno));\n}\n\nint main(int argc, const char** argv, const char** envp) {\n    g_logger = makeUnique<Hyprutils::CLI::CLoggerConnection>(*g_loggerMain);\n    g_logger->setName(\"start-hyprland\");\n    g_logger->setLogLevel(Hyprutils::CLI::LOG_DEBUG);\n\n    terminateChildOnSignal(SIGTERM);\n    terminateChildOnSignal(SIGINT);\n\n    int startArgv = -1;\n\n    for (int i = 1; i < argc; ++i) {\n        std::string_view arg = argv[i];\n\n        if (arg == \"--\") {\n            startArgv = i + 1;\n            break;\n        }\n        if (arg == \"-h\" || arg == \"--help\") {\n            std::println(\"{}\", HELP_INFO);\n            return 0;\n        }\n        if (arg == \"--path\" || arg == \"-p\") {\n            if (i + 1 >= argc) {\n                std::println(\"{} requires a path\", arg);\n                return 1;\n            }\n\n            g_state->customPath = argv[++i];\n            continue;\n        }\n        if (arg == \"--no-nixgl\") {\n            g_state->noNixGl = true;\n            continue;\n        }\n        if (arg == \"--force-nixgl\") {\n            g_state->forceNixGl = true;\n            continue;\n        }\n    }\n\n    if (startArgv != -1)\n        g_state->rawArgvNoBinPath = std::span<const char*>{argv + startArgv, argc - startArgv};\n\n    if (!g_state->rawArgvNoBinPath.empty())\n        g_logger->log(Hyprutils::CLI::LOG_WARN, \"Arguments after -- are passed to Hyprland\");\n\n    // check if our environment is OK\n    if (const auto RET = Nix::nixEnvironmentOk(); !RET) {\n        g_logger->log(Hyprutils::CLI::LOG_ERR, \"Nix environment check failed:\\n{}\", RET.error());\n        return 1;\n    }\n\n    if (Nix::shouldUseNixGL())\n        g_logger->log(Hyprutils::CLI::LOG_DEBUG, \"Hyprland was compiled with Nix - will use nixGL\");\n\n    bool safeMode = false;\n    while (true) {\n        g_instance     = makeUnique<CHyprlandInstance>();\n        const bool RET = g_instance->run(safeMode);\n        g_instance.reset();\n\n        if (!RET) {\n            g_logger->log(Hyprutils::CLI::LOG_ERR, \"Hyprland exit not-cleanly, restarting\");\n            safeMode = true;\n            continue;\n        }\n\n        g_logger->log(Hyprutils::CLI::LOG_DEBUG, \"Hyprland exit cleanly.\");\n        break;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "systemd/hyprland-uwsm.desktop",
    "content": "[Desktop Entry]\nName=Hyprland (uwsm-managed)\nComment=An intelligent dynamic tiling Wayland compositor\nExec=uwsm start -e -D Hyprland hyprland.desktop\nTryExec=uwsm\nDesktopNames=Hyprland\nType=Application\n"
  },
  {
    "path": "tests/desktop/Reserved.cpp",
    "content": "\n#include <desktop/reserved/ReservedArea.hpp>\n\n#include <gtest/gtest.h>\n\nTEST(Desktop, reservedArea) {\n    Desktop::CReservedArea a{{20, 30}, {40, 50}};\n    CBox                   box = {1000, 1000, 1000, 1000};\n    a.applyip(box);\n\n    EXPECT_EQ(box.x, 1020);\n    EXPECT_EQ(box.y, 1030);\n    EXPECT_EQ(box.w, 1000 - 20 - 40);\n    EXPECT_EQ(box.h, 1000 - 30 - 50);\n\n    box = a.apply(CBox{1000, 1000, 1000, 1000});\n\n    EXPECT_EQ(box.x, 1020);\n    EXPECT_EQ(box.y, 1030);\n    EXPECT_EQ(box.w, 1000 - 20 - 40);\n    EXPECT_EQ(box.h, 1000 - 30 - 50);\n\n    a.addType(Desktop::RESERVED_DYNAMIC_TYPE_LS, {10, 20}, {30, 40});\n\n    box = a.apply(CBox{1000, 1000, 1000, 1000});\n\n    EXPECT_EQ(box.x, 1000 + 20 + 10);\n    EXPECT_EQ(box.y, 1000 + 30 + 20);\n    EXPECT_EQ(box.w, 1000 - 20 - 40 - 10 - 30);\n    EXPECT_EQ(box.h, 1000 - 30 - 50 - 20 - 40);\n\n    Desktop::CReservedArea b{CBox{10, 10, 1000, 1000}, CBox{20, 30, 900, 900}};\n\n    EXPECT_EQ(b.left(), 20 - 10);\n    EXPECT_EQ(b.top(), 30 - 10);\n    EXPECT_EQ(b.right(), 1010 - 920);\n    EXPECT_EQ(b.bottom(), 1010 - 930);\n\n    Desktop::CReservedArea c{CBox{}, CBox{20, 30, 900, 900}};\n\n    EXPECT_EQ(c.left(), 0);\n    EXPECT_EQ(c.top(), 0);\n    EXPECT_EQ(c.right(), 0);\n    EXPECT_EQ(c.bottom(), 0);\n\n    Desktop::CReservedArea d{CBox{20, 30, 900, 900}, CBox{}};\n\n    EXPECT_EQ(d.left(), 0);\n    EXPECT_EQ(d.top(), 0);\n    EXPECT_EQ(d.right(), 0);\n    EXPECT_EQ(d.bottom(), 0);\n}"
  }
]